瀏覽代碼

Merge branch 'master' into examples_update

samuel ogundipe 6 年之前
父節點
當前提交
1e79a6d643
共有 100 個文件被更改,包括 3135 次插入737 次删除
  1. 6 4
      CHANGELOG.md
  2. 2 2
      README.md
  3. 20 2
      bin/build-bundle.js
  4. 238 0
      bin/locale-packs.js
  5. 57 25
      bin/upload-to-cdn.sh
  6. 2 2
      examples/aws-companion/main.js
  7. 3 3
      examples/bundled/index.js
  8. 1 1
      examples/custom-provider/client/MyCustomProvider.js
  9. 2 2
      examples/custom-provider/client/main.js
  10. 4 4
      examples/dev/main.js
  11. 1 1
      examples/digitalocean-spaces/main.js
  12. 1 1
      examples/react-example/App.js
  13. 4 0
      examples/transloadit-textarea/main.js
  14. 2 2
      examples/uppy-with-companion/client/index.html
  15. 7 3
      package-lock.json
  16. 7 3
      package.json
  17. 1 1
      packages/@uppy/aws-s3-multipart/README.md
  18. 3 3
      packages/@uppy/aws-s3-multipart/src/index.js
  19. 1 1
      packages/@uppy/aws-s3-multipart/types/index.d.ts
  20. 1 1
      packages/@uppy/aws-s3/README.md
  21. 5 6
      packages/@uppy/aws-s3/src/index.js
  22. 1 1
      packages/@uppy/aws-s3/types/index.d.ts
  23. 2 2
      packages/@uppy/companion-client/README.md
  24. 8 8
      packages/@uppy/companion-client/src/Provider.js
  25. 2 2
      packages/@uppy/companion-client/src/RequestClient.js
  26. 2 2
      packages/@uppy/companion-client/src/RequestClient.test.js
  27. 1 1
      packages/@uppy/companion-client/types/index.d.ts
  28. 1 1
      packages/@uppy/companion/package.json
  29. 63 54
      packages/@uppy/companion/src/server/Uploader.js
  30. 22 34
      packages/@uppy/core/src/_common.scss
  31. 23 14
      packages/@uppy/core/src/_variables.scss
  32. 21 16
      packages/@uppy/core/src/index.js
  33. 30 17
      packages/@uppy/core/src/index.test.js
  34. 1 1
      packages/@uppy/dashboard/src/components/AddFiles.js
  35. 16 10
      packages/@uppy/dashboard/src/components/FileItem.js
  36. 1 1
      packages/@uppy/dashboard/src/components/FileItemProgress.js
  37. 1 1
      packages/@uppy/dashboard/src/components/FilePreview.js
  38. 2 2
      packages/@uppy/dashboard/src/components/PickerPanelTopBar.js
  39. 14 11
      packages/@uppy/dashboard/src/components/icons.js
  40. 11 22
      packages/@uppy/dashboard/src/index.js
  41. 2 2
      packages/@uppy/dashboard/src/index.test.js
  42. 184 147
      packages/@uppy/dashboard/src/style.scss
  43. 7 7
      packages/@uppy/dashboard/src/utils/getFileTypeIcon.js
  44. 4 5
      packages/@uppy/drag-drop/src/index.js
  45. 8 8
      packages/@uppy/drag-drop/src/style.scss
  46. 3 5
      packages/@uppy/dropbox/src/index.js
  47. 2 2
      packages/@uppy/dropbox/types/index.d.ts
  48. 3 4
      packages/@uppy/file-input/src/index.js
  49. 4 4
      packages/@uppy/file-input/src/style.scss
  50. 2 2
      packages/@uppy/google-drive/src/index.js
  51. 2 2
      packages/@uppy/google-drive/types/index.d.ts
  52. 4 4
      packages/@uppy/informer/src/style.scss
  53. 1 1
      packages/@uppy/instagram/src/index.js
  54. 2 2
      packages/@uppy/instagram/types/index.d.ts
  55. 969 0
      packages/@uppy/locales/legacy/README.md
  56. 63 0
      packages/@uppy/locales/legacy/cs_CZ.js
  57. 54 0
      packages/@uppy/locales/legacy/de_DE.js
  58. 54 0
      packages/@uppy/locales/legacy/es_ES.js
  59. 54 0
      packages/@uppy/locales/legacy/fi_FI.js
  60. 54 0
      packages/@uppy/locales/legacy/id_ID.js
  61. 54 0
      packages/@uppy/locales/legacy/it_IT.js
  62. 54 0
      packages/@uppy/locales/legacy/nb_NO.js
  63. 61 0
      packages/@uppy/locales/legacy/pl_PL.js
  64. 52 0
      packages/@uppy/locales/legacy/pt_BR.js
  65. 54 0
      packages/@uppy/locales/legacy/tr_TR.js
  66. 34 0
      packages/@uppy/locales/legacy/zh_CN.js
  67. 19 0
      packages/@uppy/locales/package.json
  68. 143 0
      packages/@uppy/locales/src/en_US.js
  69. 148 0
      packages/@uppy/locales/src/ru_RU.js
  70. 18 0
      packages/@uppy/locales/template.js
  71. 3 3
      packages/@uppy/progress-bar/src/style.scss
  72. 3 3
      packages/@uppy/provider-views/src/Filter.js
  73. 4 4
      packages/@uppy/provider-views/src/index.js
  74. 92 68
      packages/@uppy/provider-views/src/style.scss
  75. 4 4
      packages/@uppy/robodog/src/addProviders.js
  76. 42 17
      packages/@uppy/status-bar/src/StatusBar.js
  77. 10 14
      packages/@uppy/status-bar/src/index.js
  78. 70 71
      packages/@uppy/status-bar/src/style.scss
  79. 53 22
      packages/@uppy/transloadit/src/AssemblyOptions.test.js
  80. 11 0
      packages/@uppy/transloadit/src/AssemblyWatcher.js
  81. 22 0
      packages/@uppy/transloadit/src/Client.js
  82. 59 16
      packages/@uppy/transloadit/src/index.js
  83. 1 1
      packages/@uppy/tus/package.json
  84. 2 1
      packages/@uppy/tus/src/index.js
  85. 7 9
      packages/@uppy/url/src/index.js
  86. 2 2
      packages/@uppy/url/src/style.scss
  87. 1 1
      packages/@uppy/url/types/index.d.ts
  88. 1 1
      packages/@uppy/webcam/src/CameraIcon.js
  89. 2 3
      packages/@uppy/webcam/src/index.js
  90. 10 10
      packages/@uppy/webcam/src/style.scss
  91. 13 10
      packages/@uppy/xhr-upload/src/index.js
  92. 2 0
      packages/uppy/index.js
  93. 2 2
      packages/uppy/types/uppy-tests.ts
  94. 1 1
      test/endtoend/create-react-app/src/App.js
  95. 3 3
      test/endtoend/providers/main.js
  96. 4 4
      test/endtoend/typescript/main.ts
  97. 1 1
      test/endtoend/url-plugin/main.js
  98. 4 1
      website/inject.js
  99. 2 2
      website/package.json
  100. 1 1
      website/src/_posts/2019-04-liftoff-14.md

+ 6 - 4
CHANGELOG.md

@@ -19,11 +19,9 @@ last Friday of every new month.
 Ideas that will be planned and find their way into a release at one point.
 PRs are welcome! Please do open an issue to discuss first if it's a big feature, priorities may have changed after something was added here.
 
-- [ ] core: Decouple rendering from the Plugin base class?
 - [ ] core: Make sure Uppy works well in VR
 - [ ] test: Human should check http://www.webpagetest.org and https://developers.google.com/web/tools/lighthouse/, use it sometimes to test website and Uppy. Will show response/loading times and other issues
 - [ ] test: Human should test with real screen reader to identify accessibility problems
-- [ ] test: setup an HTML page with all sorts of crazy styles, resets & bootstrap to see what brakes Uppy (@arturi)
 - [ ] dependencies: es6-promise --> lie https://github.com/calvinmetcalf/lie ?
 - [ ] core: accessibility research: https://chrome.google.com/webstore/detail/accessibility-developer-t/fpkknkljclfencbdbgkenhalefipecmb, http://khan.github.io/tota11y/
 - [ ] core: consider adding presets, see https://github.com/cssinjs/jss-preset-default/blob/master/src/index.js (@arturi)
@@ -89,6 +87,11 @@ PRs are welcome! Please do open an issue to discuss first if it's a big feature,
 - [ ] dragdrop: allow customizing arrow icon https://github.com/transloadit/uppy/pull/374#issuecomment-334116208 (@arturi)
 - [ ] show thumbnails when connecting with Google Drive #1162 (@ifedapoolarewaju)
 
+## 1.1
+
+- [ ] dashboard: optional alert `onbeforeunload` while upload is in progress, safeguarding from accidentaly navigating away from a page with an ongoing upload
+- [ ] dashboard: Bring back "Drop Here" screen for dragged URLs without introducing flickering (tricky! see PR #1400)
+
 ## 1.0 Goals
 
 What we need to do to release Uppy 1.0
@@ -106,8 +109,7 @@ What we need to do to release Uppy 1.0
 - [ ] website: design polish
 - [ ] companion: rename `serverUrl` and `serverPattern` to `companionUrl` and `companionAllowedHosts` (@ifedapoolarewaju)
 - [ ] transloadit: add error reporting, see https://github.com/transloadit/jquery-sdk/blob/891e99b08dd8142d8d8adc0553e6511967635ad7/js/lib/Modal.js#L122-L136 (@goto-bus-stop, @arturi)
-- [ ] transloadit: should adhere cancel event and abort assembly (@arturi, @goto-bus-stop)
-- [ ] dashboard: optional alert `onbeforeunload` while upload is in progress, safeguarding from accidentaly navigating away from a page with an ongoing upload
+- [ ] transloadit: should adhere cancel event and abort assembly (@goto-bus-stop)
 - [x] chore: remove the not-working npm scripts (@kvz, @arturi)
 - [x] build: (BREAKING) `npm run dev` no longer starts Companion by default, use `npm run dev:with-companion` for that (@arturi)
 - [x] core: uppy should not crash or be slow for many files. Specifically: be able to drop 5 files (or 7mb) without the upload button to take 2 seconds to appear

+ 2 - 2
README.md

@@ -33,8 +33,8 @@ const Tus = require('@uppy/tus')
 
 const uppy = Uppy({ autoProceed: false })
   .use(Dashboard, { trigger: '#select-files' })
-  .use(GoogleDrive, { target: Dashboard, serverUrl: 'https://companion.uppy.io' })
-  .use(Instagram, { target: Dashboard, serverUrl: 'https://companion.uppy.io' })
+  .use(GoogleDrive, { target: Dashboard, companionUrl: 'https://companion.uppy.io' })
+  .use(Instagram, { target: Dashboard, companionUrl: 'https://companion.uppy.io' })
   .use(Webcam, { target: Dashboard })
   .use(Tus, { endpoint: 'https://master.tus.io/files/' })
   .on('complete', (result) => {

+ 20 - 2
bin/build-bundle.js

@@ -5,6 +5,8 @@ var babelify = require('babelify')
 var tinyify = require('tinyify')
 var browserify = require('browserify')
 var exorcist = require('exorcist')
+var glob = require('glob')
+var path = require('path')
 
 function handleErr (err) {
   console.error(chalk.red('✗ Error:'), chalk.red(err.message))
@@ -36,8 +38,9 @@ function buildBundle (srcFile, bundleFile, { minify = false, standalone = '' } =
 
 mkdirp.sync('./packages/uppy/dist')
 mkdirp.sync('./packages/@uppy/robodog/dist')
+mkdirp.sync('./packages/@uppy/locales/dist')
 
-Promise.all([
+const methods = [
   buildBundle(
     './packages/uppy/bundle.js',
     './packages/uppy/dist/uppy.js',
@@ -58,6 +61,21 @@ Promise.all([
     './packages/@uppy/robodog/dist/robodog.min.js',
     { standalone: 'Robodog', minify: true }
   )
-]).then(function () {
+]
+
+// Build minified versions of all the locales
+const localePackagePath = path.join(__dirname, '..', 'packages', '@uppy', 'locales', 'src', '*.js')
+glob.sync(localePackagePath).forEach((localePath) => {
+  const localeName = path.basename(localePath, '.js')
+  methods.push(
+    buildBundle(
+      `./packages/@uppy/locales/src/${localeName}.js`,
+      `./packages/@uppy/locales/dist/${localeName}.min.js`,
+      { minify: true }
+    )
+  )
+})
+
+Promise.all(methods).then(function () {
   console.info(chalk.yellow('✓ JS bundles 🎉'))
 })

+ 238 - 0
bin/locale-packs.js

@@ -0,0 +1,238 @@
+const glob = require('glob')
+const Uppy = require('../packages/@uppy/core')
+const chalk = require('chalk')
+const path = require('path')
+const flat = require('flat')
+const stringifyObject = require('stringify-object')
+const fs = require('fs')
+const uppy = Uppy()
+let localePack = {}
+const plugins = {}
+const sources = {}
+
+console.warn('\n--> Make sure to run `npm run build:lib` for this locale script to work properly. ')
+
+const mode = process.argv[2]
+if (mode === 'build') {
+  build()
+} else if (mode === 'test') {
+  test()
+} else {
+  throw new Error(`First argument must be either 'build' or 'test'`)
+}
+
+function getSources (pluginName) {
+  const dependencies = {
+    // because e.g. 'companionAuthError' is used in provider-views but set in Core's defaultLocale
+    'core': ['provider-views'],
+    // because e.g. 'emptyFolderAdded' is used in provider-views but set in Dashboard's defaultLocale
+    'dashboard': ['provider-views']
+  }
+
+  const globPath = path.join(__dirname, '..', 'packages', '@uppy', pluginName, 'lib', '**', '*.js')
+  let contents = glob.sync(globPath).map((file) => {
+    return fs.readFileSync(file, 'utf-8')
+  })
+
+  if (dependencies[pluginName]) {
+    dependencies[pluginName].forEach((addPlugin) => {
+      contents = contents.concat(getSources(addPlugin))
+    })
+  }
+
+  return contents
+}
+
+function buildPluginsList () {
+  // Go over all uppy plugins, check if they are constructors
+  // and instanciate them, check for defaultLocale property,
+  // then add to plugins object
+
+  const packagesGlobPath = path.join(__dirname, '..', 'packages', '@uppy', '*', 'package.json')
+  const files = glob.sync(packagesGlobPath)
+
+  console.log(`--> Checked plugins could be instantiated and have defaultLocale in them:\n`)
+  for (let file of files) {
+    const dirName = path.dirname(file)
+    const pluginName = path.basename(dirName)
+    if (pluginName === 'locales') {
+      continue
+    }
+    const Plugin = require(dirName)
+    let plugin
+
+    // A few hacks to emulate browser environment because e.g.:
+    // GoldenRetrieves calls upon MetaDataStore in the constructor, which uses localStorage
+    // @TODO Consider rewriting constructors so they don't make imperative calls that rely on
+    // browser environment (OR: just keep this browser mocking, if it's only causing issues for this script, it doesn't matter)
+    global.location = { protocol: 'https' }
+    global.navigator = {}
+    global.localStorage = {
+      key: () => { },
+      getItem: () => { }
+    }
+    global.window = {
+      indexedDB: {
+        open: () => { return {} }
+      }
+    }
+    global.document = {
+      createElement: () => {
+        return { style: {} }
+      }
+    }
+
+    try {
+      if (pluginName === 'provider-views') {
+        plugin = new Plugin(plugins['drag-drop'], {
+          companionPattern: '',
+          companionUrl: 'https://companion.uppy.io'
+        })
+      } else if (pluginName === 'store-redux') {
+        plugin = new Plugin({ store: { dispatch: () => { } } })
+      } else {
+        plugin = new Plugin(uppy, {
+          companionPattern: '',
+          companionUrl: 'https://companion.uppy.io',
+          params: {
+            auth: {
+              key: 'x'
+            }
+          }
+        })
+      }
+    } catch (err) {
+      if (err.message !== 'Plugin is not a constructor') {
+        console.error(`--> While trying to instantiate plugin: ${pluginName}, this error was thrown: `)
+        throw err
+      }
+    }
+
+    if (plugin && plugin.defaultLocale) {
+      console.log(`[x] Check plugin: ${pluginName}`)
+      plugins[pluginName] = plugin
+      sources[pluginName] = getSources(pluginName)
+    } else {
+      console.log(`[ ] Check plugin: ${pluginName}`)
+    }
+  }
+
+  console.log(``)
+
+  return { plugins, sources }
+}
+
+function addLocaleToPack (plugin, pluginName) {
+  const localeStrings = plugin.defaultLocale.strings
+
+  for (let key in localeStrings) {
+    const valueInPlugin = JSON.stringify(localeStrings[key])
+    const valueInPack = JSON.stringify(localePack[key])
+
+    if (key in localePack && valueInPlugin !== valueInPack) {
+      console.error(`⚠ Plugin ${chalk.magenta(pluginName)} has a duplicate key: ${chalk.magenta(key)}`)
+      console.error(`  Value in plugin: ${chalk.cyan(valueInPlugin)}`)
+      console.error(`  Value in pack  : ${chalk.yellow(valueInPack)}`)
+      console.error()
+    }
+    localePack[key] = localeStrings[key]
+  }
+}
+
+function checkForUnused (fileContents, pluginName, localePack) {
+  let buff = fileContents.join('\n')
+  for (let key in localePack) {
+    let regPat = new RegExp(`(i18n|i18nArray)\\([^\\)]*['\`"]${key}['\`"]`, 'g')
+    if (!buff.match(regPat)) {
+      console.error(`⚠ defaultLocale key: ${chalk.magenta(key)} not used in plugin: ${chalk.cyan(pluginName)}`)
+    }
+  }
+}
+
+function sortObjectAlphabetically (obj, sortFunc) {
+  return Object.keys(obj).sort(sortFunc).reduce(function (result, key) {
+    result[key] = obj[key]
+    return result
+  }, {})
+}
+
+function build () {
+  const { plugins, sources } = buildPluginsList()
+
+  for (let pluginName in plugins) {
+    addLocaleToPack(plugins[pluginName], pluginName)
+  }
+
+  localePack = sortObjectAlphabetically(localePack)
+
+  for (let pluginName in sources) {
+    checkForUnused(sources[pluginName], pluginName, sortObjectAlphabetically(plugins[pluginName].defaultLocale.strings))
+  }
+
+  const prettyLocale = stringifyObject(localePack, {
+    indent: '  ',
+    singleQuotes: true,
+    inlineCharacterLimit: 12
+  })
+
+  const localeTemplatePath = path.join(__dirname, '..', 'packages', '@uppy', 'locales', 'template.js')
+  const template = fs.readFileSync(localeTemplatePath, 'utf-8')
+
+  const finalLocale = template.replace('en_US.strings = {}', 'en_US.strings = ' + prettyLocale)
+
+  const localePackagePath = path.join(__dirname, '..', 'packages', '@uppy', 'locales', 'src', 'en_US.js')
+  fs.writeFileSync(localePackagePath, finalLocale, 'utf-8')
+  console.log(`✅ Written '${localePackagePath}'`)
+}
+
+function test () {
+  const leadingLocaleName = 'en_US'
+
+  const followerLocales = {}
+  const localePackagePath = path.join(__dirname, '..', 'packages', '@uppy', 'locales', 'src', '*.js')
+  glob.sync(localePackagePath).forEach((localePath) => {
+    const localeName = path.basename(localePath, '.js')
+    // Builds array with items like: 'uploadingXFiles.2'
+    followerLocales[localeName] = Object.keys(flat(require(localePath).strings))
+  })
+
+  // Take aside our leading locale: en_US
+  const leadingLocale = followerLocales[leadingLocaleName]
+  delete followerLocales[leadingLocaleName]
+
+  // Compare all follower Locales (RU, DE, etc) with our leader en_US
+  const warnings = []
+  const fatals = []
+  for (let followerName in followerLocales) {
+    let followerLocale = followerLocales[followerName]
+    let missing = leadingLocale.filter((key) => !followerLocale.includes(key))
+    let excess = followerLocale.filter((key) => !leadingLocale.includes(key))
+
+    missing.forEach((key) => {
+      // Items missing are a non-fatal warning because we don't want CI to bum out over all languages
+      // as soon as we add some English
+      warnings.push(`${chalk.cyan(followerName)} locale has missing string: '${chalk.red(key)}' that is present in ${chalk.cyan(leadingLocaleName)}. `)
+    })
+    excess.forEach((key) => {
+      // Items in excess are a fatal because we should clean up follower languages once we remove English strings
+      fatals.push(`${chalk.cyan(followerName)} locale has excess string: '${chalk.yellow(key)}' that is not present in ${chalk.cyan(leadingLocaleName)}. `)
+    })
+  }
+
+  if (warnings.length) {
+    console.error(`--> Locale warnings: `)
+    console.error(warnings.join('\n'))
+    console.error(``)
+  }
+  if (fatals.length) {
+    console.error(`--> Locale fatal warnings: `)
+    console.error(fatals.join('\n'))
+    console.error(``)
+    process.exit(1)
+  }
+
+  if (!warnings.length && !fatals.length) {
+    console.log(`--> All locale strings have matching keys ${chalk.green(': )')}`)
+    console.log(``)
+  }
+}

+ 57 - 25
bin/upload-to-cdn.sh

@@ -33,6 +33,8 @@ __file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
 __base="$(basename ${__file} .sh)"
 __root="$(cd "$(dirname "${__dir}")" && pwd)"
 
+# versionSuffix="-test2"
+
 function fatal () {
   echo "❌ ${*}";
   exit 1
@@ -66,7 +68,7 @@ pushd "${__root}" > /dev/null 2>&1
     version="${localVersion}"
   fi
 
-  majorVersion=$(echo "${version}" |awk -F. '{print $1}')
+  majorVersion=$(echo "${version}${versionSuffix}" |awk -F. '{print $1}')
 
   echo -n "--> Check if not overwriting an existing tag ... "
   env \
@@ -74,11 +76,11 @@ pushd "${__root}" > /dev/null 2>&1
     AWS_SECRET_ACCESS_KEY="${EDGLY_SECRET}" \
   aws s3 ls \
     --region="us-east-1" \
-  "s3://crates.edgly.net/756b8efaed084669b02cb99d4540d81f/default/releases/uppy/v${version}/uppy.min.css" > /dev/null 2>&1 && fatal "Tag ${version} already exists"
+  "s3://crates.edgly.net/756b8efaed084669b02cb99d4540d81f/default/releases/uppy/v${version}${versionSuffix}/uppy.min.css" > /dev/null 2>&1 && fatal "Tag ${version}${versionSuffix} already exists"
   echo "✅"
 
 
-  echo "--> Obtain relevant npm files for robodog ${version} ... "
+  echo "--> Obtain relevant npm files for robodog ${version}${versionSuffix} ... "
   pushd packages/@uppy/robodog
     if [ -z "${remoteVersion}" ]; then
       npm pack || fatal "Unable to fetch "
@@ -101,13 +103,40 @@ pushd "${__root}" > /dev/null 2>&1
     aws s3 sync \
       --region="us-east-1" \
       --exclude 'node_modules/*' \
-    ./ "s3://crates.edgly.net/756b8efaed084669b02cb99d4540d81f/default/releases/uppy/v${version}"
-    echo "Saved https://transloadit.edgly.net/releases/uppy/v${version}/"
+    ./ "s3://crates.edgly.net/756b8efaed084669b02cb99d4540d81f/default/releases/uppy/v${version}${versionSuffix}"
+    echo "Saved https://transloadit.edgly.net/releases/uppy/v${version}${versionSuffix}/"
   popd > /dev/null 2>&1
   rm -rf /tmp/robodog-to-edgly
 
+  echo "--> Obtain relevant npm files for locales ${version}${versionSuffix} ... "
+  pushd packages/@uppy/locales
+    if [ -z "${remoteVersion}" ]; then
+      npm pack || fatal "Unable to fetch "
+    else
+      npm pack "@uppy/locales@${remoteVersion}"
+    fi
+  popd > /dev/null 2>&1
+  echo "✅"
+  rm -rf /tmp/locales-to-edgly
+  mkdir -p /tmp/locales-to-edgly
+  cp -af "packages/@uppy/locales/uppy-locales-${version}.tgz" /tmp/locales-to-edgly/
+  tar zxvf "packages/@uppy/locales/uppy-locales-${version}.tgz" -C /tmp/locales-to-edgly/
+
+  echo "--> Upload locales to edgly.net CDN"
+  pushd /tmp/locales-to-edgly/package/dist
+    # --delete \
+    env \
+      AWS_ACCESS_KEY_ID="${EDGLY_KEY}" \
+      AWS_SECRET_ACCESS_KEY="${EDGLY_SECRET}" \
+    aws s3 sync \
+      --region="us-east-1" \
+      --exclude 'node_modules/*' \
+    ./ "s3://crates.edgly.net/756b8efaed084669b02cb99d4540d81f/default/releases/uppy/v${version}${versionSuffix}/locales"
+    echo "Saved https://transloadit.edgly.net/releases/uppy/v${version}${versionSuffix}/locales/"
+  popd > /dev/null 2>&1
+  rm -rf /tmp/locales-to-edgly
 
-  echo "--> Obtain relevant npm files for uppy ${version} ... "
+  echo "--> Obtain relevant npm files for uppy ${version}${versionSuffix} ... "
   pushd packages/uppy
     if [ -z "${remoteVersion}" ]; then
       npm pack || fatal "Unable to fetch "
@@ -130,26 +159,29 @@ pushd "${__root}" > /dev/null 2>&1
     aws s3 sync \
       --region="us-east-1" \
       --exclude 'node_modules/*' \
-    ./ "s3://crates.edgly.net/756b8efaed084669b02cb99d4540d81f/default/releases/uppy/v${version}"
-    echo "Saved https://transloadit.edgly.net/releases/uppy/v${version}/"
+    ./ "s3://crates.edgly.net/756b8efaed084669b02cb99d4540d81f/default/releases/uppy/v${version}${versionSuffix}"
+    echo "Saved https://transloadit.edgly.net/releases/uppy/v${version}${versionSuffix}/"
   popd > /dev/null 2>&1
   rm -rf /tmp/uppy-to-edgly
 
-
-  echo "${version}" | env \
-    AWS_ACCESS_KEY_ID="${EDGLY_KEY}" \
-    AWS_SECRET_ACCESS_KEY="${EDGLY_SECRET}" \
-  aws s3 cp \
-    --region="us-east-1" \
-    --content-type="text/plain" \
-  - "s3://crates.edgly.net/756b8efaed084669b02cb99d4540d81f/default/releases/uppy/latest.txt"
-  echo "Saved https://transloadit.edgly.net/releases/uppy/latest.txt"
-  echo "${version}" | env \
-    AWS_ACCESS_KEY_ID="${EDGLY_KEY}" \
-    AWS_SECRET_ACCESS_KEY="${EDGLY_SECRET}" \
-  aws s3 cp \
-    --region="us-east-1" \
-    --content-type="text/plain" \
-  - "s3://crates.edgly.net/756b8efaed084669b02cb99d4540d81f/default/releases/uppy/v${majorVersion}-latest.txt"
-  echo "Saved https://transloadit.edgly.net/releases/uppy/v${majorVersion}-latest.txt"
+  if [ "${versionSuffix}" != "" ]; then
+    echo "Not setting latest version because versionSuffix was set, which is used for testing only"
+  else 
+    echo "${version}" | env \
+      AWS_ACCESS_KEY_ID="${EDGLY_KEY}" \
+      AWS_SECRET_ACCESS_KEY="${EDGLY_SECRET}" \
+    aws s3 cp \
+      --region="us-east-1" \
+      --content-type="text/plain" \
+    - "s3://crates.edgly.net/756b8efaed084669b02cb99d4540d81f/default/releases/uppy/latest.txt"
+    echo "Saved https://transloadit.edgly.net/releases/uppy/latest.txt"
+    echo "${version}" | env \
+      AWS_ACCESS_KEY_ID="${EDGLY_KEY}" \
+      AWS_SECRET_ACCESS_KEY="${EDGLY_SECRET}" \
+    aws s3 cp \
+      --region="us-east-1" \
+      --content-type="text/plain" \
+    - "s3://crates.edgly.net/756b8efaed084669b02cb99d4540d81f/default/releases/uppy/v${majorVersion}-latest.txt"
+    echo "Saved https://transloadit.edgly.net/releases/uppy/v${majorVersion}-latest.txt"
+  fi
 popd > /dev/null 2>&1

+ 2 - 2
examples/aws-companion/main.js

@@ -10,7 +10,7 @@ const uppy = Uppy({
 })
 
 uppy.use(GoogleDrive, {
-  serverUrl: 'http://localhost:3020'
+  companionUrl: 'http://localhost:3020'
 })
 uppy.use(Webcam)
 uppy.use(Dashboard, {
@@ -19,5 +19,5 @@ uppy.use(Dashboard, {
   plugins: ['GoogleDrive', 'Webcam']
 })
 uppy.use(AwsS3, {
-  serverUrl: 'http://localhost:3020'
+  companionUrl: 'http://localhost:3020'
 })

+ 3 - 3
examples/bundled/index.js

@@ -33,9 +33,9 @@ const uppy = Uppy({
     proudlyDisplayPoweredByUppy: true,
     note: '2 files, images and video only'
   })
-  .use(GoogleDrive, { target: Dashboard, serverUrl: 'http://localhost:3020' })
-  .use(Instagram, { target: Dashboard, serverUrl: 'http://localhost:3020' })
-  .use(Url, { target: Dashboard, serverUrl: 'http://localhost:3020' })
+  .use(GoogleDrive, { target: Dashboard, companionUrl: 'http://localhost:3020' })
+  .use(Instagram, { target: Dashboard, companionUrl: 'http://localhost:3020' })
+  .use(Url, { target: Dashboard, companionUrl: 'http://localhost:3020' })
   .use(Webcam, { target: Dashboard })
   .use(Tus, { endpoint: TUS_ENDPOINT })
 

+ 1 - 1
examples/custom-provider/client/MyCustomProvider.js

@@ -18,7 +18,7 @@ module.exports = class MyCustomProvider extends Plugin {
     // writing out the key explicitly for readability the key used to store
     // the provider instance must be equal to this.id.
     this[this.id] = new Provider(uppy, {
-      serverUrl: this.opts.serverUrl,
+      companionUrl: this.opts.companionUrl,
       provider: 'mycustomprovider'
     })
 

+ 2 - 2
examples/custom-provider/client/main.js

@@ -9,11 +9,11 @@ const uppy = Uppy({
 })
 
 uppy.use(GoogleDrive, {
-  serverUrl: 'http://localhost:3020'
+  companionUrl: 'http://localhost:3020'
 })
 
 uppy.use(MyCustomProvider, {
-  serverUrl: 'http://localhost:3020'
+  companionUrl: 'http://localhost:3020'
 })
 
 uppy.use(Dashboard, {

+ 4 - 4
examples/dev/main.js

@@ -31,10 +31,10 @@ const uppy = Uppy({
     proudlyDisplayPoweredByUppy: true,
     note: '2 files, images and video only'
   })
-  .use(GoogleDrive, { target: Dashboard, serverUrl: 'http://localhost:3020' })
-  .use(Instagram, { target: Dashboard, serverUrl: 'http://localhost:3020' })
-  .use(Dropbox, { target: Dashboard, serverUrl: 'http://localhost:3020' })
-  .use(Url, { target: Dashboard, serverUrl: 'http://localhost:3020' })
+  .use(GoogleDrive, { target: Dashboard, companionUrl: 'http://localhost:3020' })
+  .use(Instagram, { target: Dashboard, companionUrl: 'http://localhost:3020' })
+  .use(Dropbox, { target: Dashboard, companionUrl: 'http://localhost:3020' })
+  .use(Url, { target: Dashboard, companionUrl: 'http://localhost:3020' })
   .use(Webcam, { target: Dashboard })
   .use(Tus, { endpoint: TUS_ENDPOINT })
   // .use(XHRUpload, { endpoint: XHR_ENDPOINT })

+ 1 - 1
examples/digitalocean-spaces/main.js

@@ -12,4 +12,4 @@ uppy.use(Dashboard, {
 })
 
 // No client side changes needed!
-uppy.use(AwsS3, { serverUrl: '/companion' })
+uppy.use(AwsS3, { companionUrl: '/companion' })

+ 1 - 1
examples/react-example/App.js

@@ -16,7 +16,7 @@ module.exports = class App extends React.Component {
 
     this.uppy = new Uppy({ id: 'uppy1', autoProceed: true, debug: true })
       .use(Tus, { endpoint: 'https://master.tus.io/files/' })
-      .use(GoogleDrive, { serverUrl: 'https://companion.uppy.io' })
+      .use(GoogleDrive, { companionUrl: 'https://companion.uppy.io' })
 
     this.uppy2 = new Uppy({ id: 'uppy2', autoProceed: false, debug: true })
       .use(Tus, { endpoint: 'https://master.tus.io/files/' })

+ 4 - 0
examples/transloadit-textarea/main.js

@@ -109,6 +109,8 @@ class MarkdownTextarea {
         template_id: TRANSLOADIT_EXAMPLE_TEMPLATE
       }
     }).then((result) => {
+      // Was cancelled
+      if (result == null) return
       this.insertAttachments(
         this.matchFilesAndThumbs(result.results)
       )
@@ -126,6 +128,8 @@ class MarkdownTextarea {
         template_id: TRANSLOADIT_EXAMPLE_TEMPLATE
       }
     }).then((result) => {
+      // Was cancelled
+      if (result == null) return
       this.insertAttachments(
         this.matchFilesAndThumbs(result.results)
       )

+ 2 - 2
examples/uppy-with-companion/client/index.html

@@ -12,8 +12,8 @@
     <script>
       const uppy = Uppy.Core({debug: true, autoProceed: false})
         .use(Uppy.Dashboard, { trigger: '#uppyModalOpener' })
-        .use(Uppy.Instagram, { target: Uppy.Dashboard, serverUrl: 'http://localhost:3020' })
-        .use(Uppy.GoogleDrive, { target: Uppy.Dashboard, serverUrl: 'http://localhost:3020' })
+        .use(Uppy.Instagram, { target: Uppy.Dashboard, companionUrl: 'http://localhost:3020' })
+        .use(Uppy.GoogleDrive, { target: Uppy.Dashboard, companionUrl: 'http://localhost:3020' })
         .use(Uppy.Tus, { endpoint: 'https://master.tus.io/files/' })
 
       uppy.on('success', (fileCount) => {

+ 7 - 3
package-lock.json

@@ -43,6 +43,7 @@
     "eslint-plugin-standard": "^3.1.0",
     "exorcist": "^1.0.1",
     "fakefile": "0.0.9",
+    "flat": "^4.1.0",
     "github-contributors-list": "1.2.3",
     "glob": "^7.1.3",
     "gzip-size": "^5.0.0",
@@ -67,6 +68,7 @@
     "react-dom": "^16.8.6",
     "redux": "^4.0.1",
     "replace-x": "^1.5.0",
+    "stringify-object": "^3.3.0",
     "temp-write": "^3.4.0",
     "tinyify": "^2.5.0",
     "touch": "^3.1.0",
@@ -85,9 +87,10 @@
     "build:clean": "rm -rf packages/*/lib packages/@uppy/*/lib packages/*/dist packages/@uppy/*/dist",
     "build:companion": "cd ./packages/@uppy/companion && npm run build",
     "build:css": "node ./bin/build-css.js",
-    "build:js": "npm-run-all build:lib build:bundle",
+    "build:js": "npm-run-all build:lib build:companion build:locale-pack build:bundle",
     "build:lib": "node ./bin/build-lib.js",
-    "build": "npm-run-all --parallel build:js build:css build:companion --serial size",
+    "build:locale-pack": "node ./bin/locale-packs.js build",
+    "build": "npm-run-all --parallel build:js build:css --serial size",
     "contributors:fetch": "githubcontrib --owner transloadit --repo uppy --cols 6 $([ \"${GITHUB_TOKEN:-}\" == \"\" ] && echo \"\" || echo \"--authToken ${GITHUB_TOKEN}\") --showlogin true --sortOrder desc",
     "contributors:save": "replace-x -m '<!--contributors-->[\\s\\S]+<!--/contributors-->' \"<!--contributors-->\n## Contributors\n\n$(npm run --silent contributors:fetch)\n<!--/contributors-->\" README.md",
     "dev:browsersync": "browser-sync start --no-open --no-ghost-mode false --server examples/dev --port 3452 --serveStatic packages/uppy/dist --files \"examples/dev/bundle.js, packages/uppy/dist/uppy.min.css, packages/uppy/lib/**/*\"",
@@ -108,10 +111,11 @@
     "test:endtoend:prepare-ci": "npm-run-all --parallel --race test:endtoend:registry test:endtoend:build-ci",
     "test:endtoend:registry": "verdaccio --listen 4002 --config test/endtoend/verdaccio.yaml",
     "test:endtoend": "npm run test:endtoend:prepare-ci && wdio test/endtoend/wdio.remote.conf.js",
+    "test:locale-packs": "node ./bin/locale-packs.js test",
     "test:type": "tsc -p .",
     "test:unit": "npm run build:lib && jest",
     "test:watch": "jest --watch",
-    "test": "npm-run-all lint test:unit test:type test:companion",
+    "test": "npm-run-all lint test:locale-packs test:unit test:type test:companion",
     "uploadcdn": "bin/upload-to-cdn.sh",
     "version": "./bin/sync-version-numbers",
     "watch:css": "onchange 'packages/**/*.scss' --initial --verbose -- npm run build:css",

+ 7 - 3
package.json

@@ -43,6 +43,7 @@
     "eslint-plugin-standard": "^3.1.0",
     "exorcist": "^1.0.1",
     "fakefile": "0.0.9",
+    "flat": "^4.1.0",
     "github-contributors-list": "1.2.3",
     "glob": "^7.1.3",
     "gzip-size": "^5.0.0",
@@ -67,6 +68,7 @@
     "react-dom": "^16.8.6",
     "redux": "^4.0.1",
     "replace-x": "^1.5.0",
+    "stringify-object": "^3.3.0",
     "temp-write": "^3.4.0",
     "tinyify": "^2.5.0",
     "touch": "^3.1.0",
@@ -85,9 +87,10 @@
     "build:clean": "rm -rf packages/*/lib packages/@uppy/*/lib packages/*/dist packages/@uppy/*/dist",
     "build:companion": "cd ./packages/@uppy/companion && npm run build",
     "build:css": "node ./bin/build-css.js",
-    "build:js": "npm-run-all build:lib build:bundle",
+    "build:js": "npm-run-all build:lib build:companion build:locale-pack build:bundle",
     "build:lib": "node ./bin/build-lib.js",
-    "build": "npm-run-all --parallel build:js build:css build:companion --serial size",
+    "build:locale-pack": "node ./bin/locale-packs.js build",
+    "build": "npm-run-all --parallel build:js build:css --serial size",
     "contributors:fetch": "githubcontrib --owner transloadit --repo uppy --cols 6 $([ \"${GITHUB_TOKEN:-}\" == \"\" ] && echo \"\" || echo \"--authToken ${GITHUB_TOKEN}\") --showlogin true --sortOrder desc",
     "contributors:save": "replace-x -m '<!--contributors-->[\\s\\S]+<!--/contributors-->' \"<!--contributors-->\n## Contributors\n\n$(npm run --silent contributors:fetch)\n<!--/contributors-->\" README.md",
     "dev:browsersync": "browser-sync start --no-open --no-ghost-mode false --server examples/dev --port 3452 --serveStatic packages/uppy/dist --files \"examples/dev/bundle.js, packages/uppy/dist/uppy.min.css, packages/uppy/lib/**/*\"",
@@ -108,10 +111,11 @@
     "test:endtoend:prepare-ci": "npm-run-all --parallel --race test:endtoend:registry test:endtoend:build-ci",
     "test:endtoend:registry": "verdaccio --listen 4002 --config test/endtoend/verdaccio.yaml",
     "test:endtoend": "npm run test:endtoend:prepare-ci && wdio test/endtoend/wdio.remote.conf.js",
+    "test:locale-packs": "node ./bin/locale-packs.js test",
     "test:type": "tsc -p .",
     "test:unit": "npm run build:lib && jest",
     "test:watch": "jest --watch",
-    "test": "npm-run-all lint test:unit test:type test:companion",
+    "test": "npm-run-all lint test:locale-packs test:unit test:type test:companion",
     "uploadcdn": "bin/upload-to-cdn.sh",
     "version": "./bin/sync-version-numbers",
     "watch:css": "onchange 'packages/**/*.scss' --initial --verbose -- npm run build:css",

+ 1 - 1
packages/@uppy/aws-s3-multipart/README.md

@@ -18,7 +18,7 @@ const AwsS3Multipart = require('@uppy/aws-s3-multipart')
 const uppy = Uppy()
 uppy.use(AwsS3Multipart, {
   limit: 2,
-  serverUrl: 'https://companion.myapp.com/'
+  companionUrl: 'https://companion.myapp.com/'
 })
 ```
 

+ 3 - 3
packages/@uppy/aws-s3-multipart/src/index.js

@@ -86,8 +86,8 @@ module.exports = class AwsS3Multipart extends Plugin {
   }
 
   assertHost () {
-    if (!this.opts.serverUrl) {
-      throw new Error('Expected a `serverUrl` option containing a Companion address.')
+    if (!this.opts.companionUrl) {
+      throw new Error('Expected a `companionUrl` option containing a Companion address.')
     }
   }
 
@@ -278,7 +278,7 @@ module.exports = class AwsS3Multipart extends Plugin {
   connectToServerSocket (file) {
     return new Promise((resolve, reject) => {
       const token = file.serverToken
-      const host = getSocketHost(file.remote.serverUrl)
+      const host = getSocketHost(file.remote.companionUrl)
       const socket = new Socket({ target: `${host}/api/${token}` })
       this.uploaderSockets[socket] = socket
       this.uploaderEvents[file.id] = createEventTracker(this.uppy)

+ 1 - 1
packages/@uppy/aws-s3-multipart/types/index.d.ts

@@ -8,7 +8,7 @@ declare module AwsS3Multipart {
   }
 
   interface AwsS3MultipartOptions extends Uppy.PluginOptions {
-    serverUrl: string;
+    companionUrl: string;
     createMultipartUpload(file: Uppy.UppyFile): Promise<{ uploadId: string, key: string }>;
     listParts(file: Uppy.UppyFile, opts: { uploadId: string, key: string }): Promise<AwsS3Part[]>;
     prepareUploadPart(file: Uppy.UppyFile, partData: { uploadId: string, key: string, body: Blob, number: number }): Promise<{ url: string }>;

+ 1 - 1
packages/@uppy/aws-s3/README.md

@@ -19,7 +19,7 @@ const uppy = Uppy()
 uppy.use(AwsS3, {
   limit: 2,
   timeout: ms('1 minute'),
-  serverUrl: 'https://companion.myapp.com/'
+  companionUrl: 'https://companion.myapp.com/'
 })
 ```
 

+ 5 - 6
packages/@uppy/aws-s3/src/index.js

@@ -34,7 +34,7 @@ module.exports = class AwsS3 extends Plugin {
     this.id = 'AwsS3'
     this.title = 'AWS S3'
 
-    const defaultLocale = {
+    this.defaultLocale = {
       strings: {
         preparingUpload: 'Preparing upload...'
       }
@@ -43,14 +43,13 @@ module.exports = class AwsS3 extends Plugin {
     const defaultOptions = {
       timeout: 30 * 1000,
       limit: 0,
-      getUploadParameters: this.getUploadParameters.bind(this),
-      locale: defaultLocale
+      getUploadParameters: this.getUploadParameters.bind(this)
     }
 
     this.opts = { ...defaultOptions, ...opts }
 
     // i18n
-    this.translator = new Translator([ defaultLocale, this.uppy.locale, this.opts.locale ])
+    this.translator = new Translator([ this.defaultLocale, this.uppy.locale, this.opts.locale ])
     this.i18n = this.translator.translate.bind(this.translator)
     this.i18nArray = this.translator.translateArray.bind(this.translator)
 
@@ -66,8 +65,8 @@ module.exports = class AwsS3 extends Plugin {
   }
 
   getUploadParameters (file) {
-    if (!this.opts.serverUrl) {
-      throw new Error('Expected a `serverUrl` option containing a Companion address.')
+    if (!this.opts.companionUrl) {
+      throw new Error('Expected a `companionUrl` option containing a Companion address.')
     }
 
     const filename = encodeURIComponent(file.meta.name)

+ 1 - 1
packages/@uppy/aws-s3/types/index.d.ts

@@ -9,7 +9,7 @@ declare module AwsS3 {
   }
 
   interface AwsS3Options extends Uppy.PluginOptions {
-    serverUrl: string;
+    companionUrl: string;
     getUploadParameters(file: Uppy.UppyFile): Promise<AwsS3UploadParameters>;
     timeout: number;
     limit: number;

+ 2 - 2
packages/@uppy/companion-client/README.md

@@ -17,11 +17,11 @@ const { Provider, RequestClient, Socket } = require('@uppy/companion-client')
 
 const uppy = Uppy()
 
-const client = new RequestClient(uppy, { serverUrl: 'https://uppy.mywebsite.com/' })
+const client = new RequestClient(uppy, { companionUrl: 'https://uppy.mywebsite.com/' })
 client.get('/drive/list').then(() => {})
 
 const provider = new Provider(uppy, {
-  serverUrl: 'https://uppy.mywebsite.com/',
+  companionUrl: 'https://uppy.mywebsite.com/',
   provider: providerPluginInstance
 })
 provider.checkAuth().then(() => {})

+ 8 - 8
packages/@uppy/companion-client/src/Provider.js

@@ -74,19 +74,19 @@ module.exports = class Provider extends RequestClient {
       plugin.opts = Object.assign({}, defaultOpts, opts)
     }
 
-    if (opts.serverPattern) {
-      const pattern = opts.serverPattern
-      // validate serverPattern param
+    if (opts.companionAllowedHosts) {
+      const pattern = opts.companionAllowedHosts
+      // validate companionAllowedHosts param
       if (typeof pattern !== 'string' && !Array.isArray(pattern) && !(pattern instanceof RegExp)) {
-        throw new TypeError(`${plugin.id}: the option "serverPattern" must be one of string, Array, RegExp`)
+        throw new TypeError(`${plugin.id}: the option "companionAllowedHosts" must be one of string, Array, RegExp`)
       }
-      plugin.opts.serverPattern = pattern
+      plugin.opts.companionAllowedHosts = pattern
     } else {
       // does not start with https://
-      if (/^(?!https?:\/\/).*$/i.test(opts.serverUrl)) {
-        plugin.opts.serverPattern = `https://${opts.serverUrl.replace(/^\/\//, '')}`
+      if (/^(?!https?:\/\/).*$/i.test(opts.companionUrl)) {
+        plugin.opts.companionAllowedHosts = `https://${opts.companionUrl.replace(/^\/\//, '')}`
       } else {
-        plugin.opts.serverPattern = opts.serverUrl
+        plugin.opts.companionAllowedHosts = opts.companionUrl
       }
     }
 

+ 2 - 2
packages/@uppy/companion-client/src/RequestClient.js

@@ -16,7 +16,7 @@ module.exports = class RequestClient {
 
   get hostname () {
     const { companion } = this.uppy.getState()
-    const host = this.opts.serverUrl
+    const host = this.opts.companionUrl
     return stripSlash(companion && companion[host] ? companion[host] : host)
   }
 
@@ -44,7 +44,7 @@ module.exports = class RequestClient {
   onReceiveResponse (response) {
     const state = this.uppy.getState()
     const companion = state.companion || {}
-    const host = this.opts.serverUrl
+    const host = this.opts.companionUrl
     const headers = response.headers
     // Store the self-identified domain name for the Companion instance we just hit.
     if (headers.has('i-am') && headers.get('i-am') !== companion[host]) {

+ 2 - 2
packages/@uppy/companion-client/src/RequestClient.test.js

@@ -3,8 +3,8 @@ const RequestClient = require('./RequestClient')
 describe('RequestClient', () => {
   it('has a hostname without trailing slash', () => {
     const mockCore = { getState: () => ({}) }
-    const a = new RequestClient(mockCore, { serverUrl: 'http://companion.uppy.io' })
-    const b = new RequestClient(mockCore, { serverUrl: 'http://companion.uppy.io/' })
+    const a = new RequestClient(mockCore, { companionUrl: 'http://companion.uppy.io' })
+    const b = new RequestClient(mockCore, { companionUrl: 'http://companion.uppy.io/' })
 
     expect(a.hostname).toBe('http://companion.uppy.io')
     expect(b.hostname).toBe('http://companion.uppy.io')

+ 1 - 1
packages/@uppy/companion-client/types/index.d.ts

@@ -1,7 +1,7 @@
 import Uppy = require('@uppy/core');
 
 export interface RequestClientOptions {
-  serverUrl: string;
+  companionUrl: string;
   serverHeaders?: object;
 }
 

+ 1 - 1
packages/@uppy/companion/package.json

@@ -55,7 +55,7 @@
     "redis": "2.8.0",
     "request": "2.85.0",
     "serialize-error": "^2.1.0",
-    "tus-js-client": "github:ifedapoolarewaju/tus-js-client#888bcf73b66698a165f086f7bbe61951597f5c1b",
+    "tus-js-client": "github:ifedapoolarewaju/tus-js-client#d63fcf08ee43080a0e1cc968ad5986ace788c2d4",
     "uuid": "2.0.2",
     "validator": "^9.0.0",
     "ws": "1.1.5"

+ 63 - 54
packages/@uppy/companion/src/server/Uploader.js

@@ -12,6 +12,12 @@ const logger = require('./logger')
 const validator = require('validator')
 const headerSanitize = require('./header-blacklist')
 
+const PROTOCOLS = Object.freeze({
+  multipart: 'multipart',
+  s3Multipart: 's3-multipart',
+  tus: 'tus'
+})
+
 class Uploader {
   /**
    * Uploads file to destination based on the supplied protocol (tus, s3-multipart, multipart)
@@ -44,8 +50,11 @@ class Uploader {
     this.token = uuid.v4()
     this.options.path = `${this.options.pathPrefix}/${Uploader.FILE_NAME_PREFIX}-${this.token}`
     this.streamsEnded = false
-    this.duplexStream = new stream.PassThrough()
-      .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.duplex.error'))
+    this.duplexStream = null
+    if (this.options.protocol === PROTOCOLS.tus) {
+      this.duplexStream = new stream.PassThrough()
+        .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.duplex.error'))
+    }
     this.writeStream = fs.createWriteStream(this.options.path, { mode: 0o666 }) // no executable files
       .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.write.error'))
     /** @type {number} */
@@ -70,7 +79,7 @@ class Uploader {
     // s3 uploads don't require upload destination
     // validation, because the destination is determined
     // by the server's s3 config
-    if (options.protocol === 's3-multipart') {
+    if (options.protocol === PROTOCOLS.s3Multipart) {
       return true
     }
 
@@ -131,56 +140,34 @@ class Uploader {
    * @param {Buffer | Buffer[]} chunk
    */
   handleChunk (chunk) {
-    logger.debug(`${this.shortToken} ${this.bytesWritten} bytes`, 'uploader.download.progress')
-
-    const protocol = this.options.protocol || 'multipart'
+    // @todo a default protocol should not be set. We should ensure that the user specifies her protocol.
+    const protocol = this.options.protocol || PROTOCOLS.multipart
 
     // The download has completed; close the file and start an upload if necessary.
     if (chunk === null) {
       this.writeStream.on('finish', () => {
         this.streamsEnded = true
-        if (this.options.endpoint && protocol === 'multipart') {
+        if (this.options.endpoint && protocol === PROTOCOLS.multipart) {
           this.uploadMultipart()
         }
       })
 
-      this.duplexStream.end()
-      return this.writeStream.end()
+      return this.endStreams()
     }
 
     this.writeToStreams(chunk, () => {
-      if (protocol === 's3-multipart' && !this.s3Upload) {
-        return this.uploadS3Streaming()
+      logger.debug(`${this.shortToken} ${this.bytesWritten} bytes`, 'uploader.download.progress')
+      if (protocol === PROTOCOLS.multipart) {
+        return this.emitIllusiveProgress()
       }
-      if (!this.options.endpoint) return
-
-      if (protocol === 'tus' && !this.tus) {
-        return this.uploadTus(true)
-      }
-    })
-  }
 
-  /**
-   *
-   * @param {object} resp
-   */
-  handleResponse (resp) {
-    resp.pipe(this.writeStream)
-
-    const protocol = this.options.protocol || 'multipart'
-
-    this.writeStream.on('finish', () => {
-      if (protocol === 's3-multipart') {
-        this.uploadS3Full()
+      if (protocol === PROTOCOLS.s3Multipart && !this.s3Upload) {
+        return this.uploadS3()
       }
-
       if (!this.options.endpoint) return
 
-      if (protocol === 'tus') {
-        this.uploadTus(false)
-      }
-      if (protocol === 'multipart') {
-        this.uploadMultipart()
+      if (protocol === PROTOCOLS.tus && !this.tus) {
+        return this.uploadTus()
       }
     })
   }
@@ -191,15 +178,25 @@ class Uploader {
    */
   writeToStreams (chunk, cb) {
     const done = []
+    const doneLength = this.duplexStream ? 2 : 1
     const onDone = () => {
       done.push(true)
-      if (done.length >= 2) {
+      if (done.length >= doneLength) {
         cb()
       }
     }
 
-    this.duplexStream.write(chunk, onDone)
     this.writeStream.write(chunk, onDone)
+    if (this.duplexStream) {
+      this.duplexStream.write(chunk, onDone)
+    }
+  }
+
+  endStreams () {
+    this.writeStream.end()
+    if (this.duplexStream) {
+      this.duplexStream.end()
+    }
   }
 
   getResponse () {
@@ -218,6 +215,27 @@ class Uploader {
     this.storage.set(`${Uploader.STORAGE_PREFIX}:${this.token}`, jsonStringify(state))
   }
 
+  /**
+   * This method emits upload progress but also creates an "upload progress" illusion
+   * for the waiting period while only download is happening. Hence, it combines both
+   * download and upload into an upload progress.
+   * @see emitProgress
+   * @param {number=} bytesUploaded the bytes actually Uploaded so far
+   */
+  emitIllusiveProgress (bytesUploaded) {
+    const bytesTotal = this.streamsEnded ? this.bytesWritten : this.options.size
+    bytesUploaded = bytesUploaded || 0
+    // for a 10MB file, 10MB of download will account for 5MB upload progress
+    // and 10MB of actual upload will account for the other 5MB upload progress.
+    const illusiveBytesUploaded = (this.bytesWritten / 2) + (bytesUploaded / 2)
+
+    logger.debug(
+      `${this.shortToken} ${bytesUploaded} ${illusiveBytesUploaded} ${bytesTotal}`,
+      'uploader.illusive.progress'
+    )
+    this.emitProgress(illusiveBytesUploaded, bytesTotal)
+  }
+
   /**
    *
    * @param {number} bytesUploaded
@@ -279,14 +297,13 @@ class Uploader {
   }
 
   /**
-   *
-   * @param {boolean} deferLength
+   * start the tus upload
    */
-  uploadTus (deferLength) {
+  uploadTus () {
     const fname = path.basename(this.options.path)
     const ftype = this.options.metadata.type
     const metadata = Object.assign({ filename: fname, filetype: ftype }, this.options.metadata || {})
-    const file = deferLength ? this.duplexStream : fs.createReadStream(this.options.path)
+    const file = this.duplexStream
     const uploader = this
     const oneGB = 1024 * 1024 * 1024  // 1 GB
     // chunk size can't be infinity with deferred length.
@@ -298,9 +315,9 @@ class Uploader {
       endpoint: this.options.endpoint,
       uploadUrl: this.options.uploadUrl,
       // @ts-ignore
-      uploadLengthDeferred: deferLength,
+      uploadLengthDeferred: true,
       resume: true,
-      uploadSize: deferLength ? null : (this.options.size || fs.statSync(this.options.path).size),
+      uploadSize: null,
       metadata,
       chunkSize,
       /**
@@ -343,7 +360,7 @@ class Uploader {
     let bytesUploaded = 0
     file.on('data', (data) => {
       bytesUploaded += data.length
-      this.emitProgress(bytesUploaded, null)
+      this.emitIllusiveProgress(bytesUploaded)
     })
 
     const formData = Object.assign(
@@ -384,7 +401,7 @@ class Uploader {
   /**
    * Upload the file to S3 while it is still being downloaded.
    */
-  uploadS3Streaming () {
+  uploadS3 () {
     const file = createTailReadStream(this.options.path, {
       tail: true
     })
@@ -396,14 +413,6 @@ class Uploader {
     return this._uploadS3(file)
   }
 
-  /**
-   * Upload the file to S3 after it has been fully downloaded.
-   */
-  uploadS3Full () {
-    const file = fs.createReadStream(this.options.path)
-    return this._uploadS3(file)
-  }
-
   /**
    * Upload a stream to S3.
    */

+ 22 - 34
packages/@uppy/core/src/_common.scss

@@ -10,24 +10,13 @@
   -moz-osx-font-smoothing: grayscale;
   text-align: left;
   position: relative;
+  color: $gray-800;
 }
 
 .uppy-Root *, .uppy-Root *:before, .uppy-Root *:after {
   box-sizing: inherit;
 }
 
-// .uppy-Root *:focus:not(.focus-visible) {
-//   outline: 0;
-// }
-
-.uppy-Root *:focus {
-  outline: $size-focus-outline solid $color-cornflower-blue; /* no !important */
-  outline-offset: $size-focus-offset; /* no !important */
-
-  // outline: none;
-  // box-shadow: inset 0 0 0 2px rgba($color-cornflower-blue, 0.5);
-}
-
 .uppy-Root [hidden] {
   display: none;
 }
@@ -109,24 +98,22 @@
 // Inputs
 
 .uppy-c-textInput {
-  border: 1px solid rgba($color-gray, 0.5);
+  border: 1px solid #ddd;
   border-radius: 4px;
-  font-size: 13px;
+  font-size: 14px;
   line-height: 1.5;
   padding: 6px 8px;
-  background-color: $color-white;
+  background-color: $white;
 }
 
   .uppy-size--md .uppy-c-textInput {
-    font-size: 15px;
-    line-height: 1.8;
-    padding: 8px 12px;
+    padding: 8px 10px;
   }
 
   .uppy-c-textInput:focus {
-    border-color: $color-cornflower-blue;
+    border-color: rgba($blue, 0.6);
     outline: none;
-    box-shadow: 0 0 1px 1px rgba($color-cornflower-blue, 0.5);
+    box-shadow: 0 0 0 3px rgba($blue, 0.15);
   }
 
 // Buttons
@@ -148,50 +135,51 @@
     cursor: pointer;
   }
 
+.uppy-c-btn::-moz-focus-inner {
+   border: 0;
+}
+
 .uppy-c-btn-primary {
-  font-size: 13px;
+  font-size: 14px;
   padding: 10px 18px;
   border-radius: 4px;
-  background-color: $color-cornflower-blue;
-  color: $color-white;
+  background-color: $blue;
+  color: $white;
 }
 
   .uppy-size--md .uppy-c-btn-primary {
-    font-size: 15px;
     padding: 13px 22px;
   }
 
   .uppy-c-btn-primary:hover {
-    background-color: darken($color-cornflower-blue, 10%);
+    background-color: darken($blue, 10%);
   }
 
   .uppy-c-btn-primary:focus {
     outline: none;
-    box-shadow: 0 0 0 3px rgba($color-cornflower-blue, 0.5);
+    box-shadow: 0 0 0 3px rgba($blue, 0.4);
   }
 
 .uppy-c-btn-link {
-  font-size: 13px;
+  font-size: 14px;
   line-height: 1;
-  padding: 10px 18px;
+  padding: 10px 15px;
   border-radius: 4px;
   background-color: transparent;
-  color: $color-black;
+  color: $gray-700;
 }
 
   .uppy-size--md .uppy-c-btn-link {
-    font-size: 15px;
-    padding: 13px 28px;
-    // border-radius: 4px;
+    padding: 13px 18px;
   }
 
   .uppy-c-btn-link:hover {
-    text-decoration: underline;
+    color: $gray-800;
   }
 
   .uppy-c-btn-link:focus {
     outline: none;
-    box-shadow: 0 0 0 0.2rem rgba($color-cornflower-blue, 0.5);
+    box-shadow: 0 0 0 3px rgba($blue, 0.25);
   }
 
 .uppy-c-btn--small {

+ 23 - 14
packages/@uppy/core/src/_variables.scss

@@ -1,21 +1,30 @@
 // Fonts
-$font-family-base: system-ui, -apple-system, BLinkMacSystemFont, Segoe UI, Roboto,
-                   Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif !default;
+$font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol' !default;
 
 // Colors
 
-$color-black: #000 !default;
-$color-gray: #939393 !default;
-$color-red: #D32F2F !default;
-$color-green: #1BB240 !default;
-$color-orange: #F6A623 !default;
-$color-yellow: #FFD600 !default;
-$color-white: #fff !default;
-$color-almost-white: #FAFAFA !default;
-$color-cornflower-blue: #2275D7 !default;
-$color-asphalt-gray: #525252 !default;
-
-$color-uppy-pink: #EB2177;
+$white: #fff !default;
+$black: #000 !default;
+$red: #e32437 !default;
+$pomegranate: #ff4b23 !default;
+$orange: #f6a623 !default;
+$yellow: #ffd600 !default;
+$green: #1bb240 !default;
+$blue: #2275d7 !default;
+
+$gray-50: #fafafa !default;
+$gray-100: #f4f4f4 !default;
+$gray-100--highlighted: #f1f3f6;
+$gray-200: #eaeaea !default;
+$gray-300: #cfcfcf !default;
+$gray-400: #bbb !default;
+$gray-500: #939393 !default;
+$gray-600: #757575 !default;
+$gray-700: #525252 !default;
+$gray-800: #333 !default;
+$gray-900: #1f1f1f !default;
+
+$uppy-pink: #eb2177;
 
 // Sizes
 

+ 21 - 16
packages/@uppy/core/src/index.js

@@ -23,15 +23,17 @@ class Uppy {
   * @param {object} opts — Uppy options
   */
   constructor (opts) {
-    const defaultLocale = {
+    this.defaultLocale = {
       strings: {
         youCanOnlyUploadX: {
           0: 'You can only upload %{smart_count} file',
-          1: 'You can only upload %{smart_count} files'
+          1: 'You can only upload %{smart_count} files',
+          2: 'You can only upload %{smart_count} files'
         },
         youHaveToAtLeastSelectX: {
           0: 'You have to select at least %{smart_count} file',
-          1: 'You have to select at least %{smart_count} files'
+          1: 'You have to select at least %{smart_count} files',
+          2: 'You have to select at least %{smart_count} files'
         },
         exceedsSize: 'This file exceeds maximum allowed size of',
         youCanOnlyUploadFileTypes: 'You can only upload: %{types}',
@@ -44,7 +46,8 @@ class Uppy {
         noFilesFound: 'You have no files or folders here',
         selectXFiles: {
           0: 'Select %{smart_count} file',
-          1: 'Select %{smart_count} files'
+          1: 'Select %{smart_count} files',
+          2: 'Select %{smart_count} files'
         },
         cancel: 'Cancel',
         logOut: 'Log out',
@@ -68,7 +71,6 @@ class Uppy {
       meta: {},
       onBeforeFileAdded: (currentFile, files) => currentFile,
       onBeforeUpload: (files) => files,
-      locale: defaultLocale,
       store: DefaultStore()
     }
 
@@ -77,7 +79,7 @@ class Uppy {
     this.opts.restrictions = Object.assign({}, defaultOptions.restrictions, this.opts.restrictions)
 
     // i18n
-    this.translator = new Translator([ defaultLocale, this.opts.locale ])
+    this.translator = new Translator([ this.defaultLocale, this.opts.locale ])
     this.locale = this.translator.locale
     this.i18n = this.translator.translate.bind(this.translator)
 
@@ -123,6 +125,7 @@ class Uppy {
       allowNewUpload: true,
       capabilities: {
         uploadProgress: supportsUploadProgress(),
+        individualCancellation: true,
         resumableUploads: false
       },
       totalProgress: 0,
@@ -454,6 +457,7 @@ class Uppy {
     try {
       this._checkRestrictions(newFile)
     } catch (err) {
+      this.emit('restriction-failed', newFile, err)
       onError(err)
     }
 
@@ -1013,29 +1017,27 @@ class Uppy {
   /**
    * Logs stuff to console, only if `debug` is set to true. Silent in production.
    *
-   * @param {String|Object} msg to log
+   * @param {String|Object} message to log
    * @param {String} [type] optional `error` or `warning`
    */
-  log (msg, type) {
+  log (message, type) {
     if (!this.opts.debug) {
       return
     }
 
-    let message = `[Uppy] [${getTimeStamp()}] ${msg}`
-
-    window['uppyLog'] = window['uppyLog'] + '\n' + 'DEBUG LOG: ' + msg
+    const prefix = `[Uppy] [${getTimeStamp()}]`
 
     if (type === 'error') {
-      console.error(message)
+      console.error(prefix, message)
       return
     }
 
     if (type === 'warning') {
-      console.warn(message)
+      console.warn(prefix, message)
       return
     }
 
-    console.log(message)
+    console.log(prefix, message)
   }
 
   /**
@@ -1189,7 +1191,6 @@ class Uppy {
       const { currentUploads } = this.getState()
       const currentUpload = currentUploads[uploadID]
       if (!currentUpload) {
-        this.log(`Not setting result for an upload that has been removed: ${uploadID}`)
         return
       }
 
@@ -1205,7 +1206,6 @@ class Uppy {
       // to an outdated object without the `.result` property.
       const { currentUploads } = this.getState()
       if (!currentUploads[uploadID]) {
-        this.log(`Not setting result for an upload that has been canceled: ${uploadID}`)
         return
       }
       const currentUpload = currentUploads[uploadID]
@@ -1214,6 +1214,11 @@ class Uppy {
 
       this._removeUpload(uploadID)
 
+      return result
+    }).then((result) => {
+      if (result == null) {
+        this.log(`Not setting result for an upload that has been removed: ${uploadID}`)
+      }
       return result
     })
   }

+ 30 - 17
packages/@uppy/core/src/index.test.js

@@ -1,5 +1,6 @@
 const fs = require('fs')
 const path = require('path')
+const prettyBytes = require('prettier-bytes')
 const Core = require('./index')
 const Plugin = require('./Plugin')
 const AcquirerPlugin1 = require('../../../../test/mocks/acquirerPlugin1')
@@ -149,7 +150,7 @@ describe('src/Core', () => {
 
       const newState = {
         bee: 'boo',
-        capabilities: { uploadProgress: true, resumableUploads: false },
+        capabilities: { individualCancellation: true, uploadProgress: true, resumableUploads: false },
         files: {},
         currentUploads: {},
         allowNewUpload: true,
@@ -173,7 +174,7 @@ describe('src/Core', () => {
       // current state
       expect(stateUpdateEventMock.mock.calls[1][0]).toEqual({
         bee: 'boo',
-        capabilities: { uploadProgress: true, resumableUploads: false },
+        capabilities: { individualCancellation: true, uploadProgress: true, resumableUploads: false },
         files: {},
         currentUploads: {},
         allowNewUpload: true,
@@ -186,7 +187,7 @@ describe('src/Core', () => {
       // new state
       expect(stateUpdateEventMock.mock.calls[1][1]).toEqual({
         bee: 'boo',
-        capabilities: { uploadProgress: true, resumableUploads: false },
+        capabilities: { individualCancellation: true, uploadProgress: true, resumableUploads: false },
         files: {},
         currentUploads: {},
         allowNewUpload: true,
@@ -203,17 +204,7 @@ describe('src/Core', () => {
 
       core.setState({ foo: 'bar' })
 
-      expect(core.getState()).toEqual({
-        capabilities: { uploadProgress: true, resumableUploads: false },
-        files: {},
-        currentUploads: {},
-        allowNewUpload: true,
-        foo: 'bar',
-        info: { isHidden: true, message: '', type: 'info' },
-        meta: {},
-        plugins: {},
-        totalProgress: 0
-      })
+      expect(core.getState()).toMatchObject({ foo: 'bar' })
     })
   })
 
@@ -228,11 +219,10 @@ describe('src/Core', () => {
 
     core.reset()
 
-    // expect(corePauseEventMock.mock.calls.length).toEqual(1)
     expect(coreCancelEventMock.mock.calls.length).toEqual(1)
     expect(coreStateUpdateEventMock.mock.calls.length).toEqual(2)
     expect(coreStateUpdateEventMock.mock.calls[1][1]).toEqual({
-      capabilities: { uploadProgress: true, resumableUploads: false },
+      capabilities: { individualCancellation: true, uploadProgress: true, resumableUploads: false },
       files: {},
       currentUploads: {},
       allowNewUpload: true,
@@ -291,7 +281,7 @@ describe('src/Core', () => {
     expect(coreCancelEventMock.mock.calls.length).toEqual(1)
     expect(coreStateUpdateEventMock.mock.calls.length).toEqual(1)
     expect(coreStateUpdateEventMock.mock.calls[0][1]).toEqual({
-      capabilities: { uploadProgress: true, resumableUploads: false },
+      capabilities: { individualCancellation: true, uploadProgress: true, resumableUploads: false },
       files: {},
       currentUploads: {},
       allowNewUpload: true,
@@ -1270,6 +1260,29 @@ describe('src/Core', () => {
         expect(core.getState().info.message).toEqual('This file exceeds maximum allowed size of 1.2 KB')
       }
     })
+
+    it('should emit `restriction-failed` event when some rule is violated', () => {
+      const maxFileSize = 100
+      const core = new Core({
+        restrictions: {
+          maxFileSize
+        }
+      })
+      const restrictionsViolatedEventMock = jest.fn()
+      const file = {
+        name: 'test.jpg',
+        data: new Blob([Buffer.alloc(2 * maxFileSize)])
+      }
+      const errorMessage = `${core.i18n('exceedsSize')} ${prettyBytes(maxFileSize)}`
+      try {
+        core.on('restriction-failed', restrictionsViolatedEventMock)
+        core.addFile(file)
+      } catch (err) {}
+
+      expect(restrictionsViolatedEventMock.mock.calls.length).toEqual(1)
+      expect(restrictionsViolatedEventMock.mock.calls[0][0].name).toEqual(file.name)
+      expect(restrictionsViolatedEventMock.mock.calls[0][1].message).toEqual(errorMessage)
+    })
   })
 
   describe('actions', () => {

+ 1 - 1
packages/@uppy/dashboard/src/components/AddFiles.js

@@ -3,7 +3,7 @@ const { localIcon } = require('./icons')
 const { h, Component } = require('preact')
 
 const poweredByUppy = (props) => {
-  return <a tabindex="-1" href="https://uppy.io" rel="noreferrer noopener" target="_blank" class="uppy-Dashboard-poweredBy">Powered by <svg aria-hidden="true" class="UppyIcon uppy-Dashboard-poweredByIcon" width="11" height="11" viewBox="0 0 11 11" xmlns="http://www.w3.org/2000/svg">
+  return <a tabindex="-1" href="https://uppy.io" rel="noreferrer noopener" target="_blank" class="uppy-Dashboard-poweredBy">Powered by <svg aria-hidden="true" class="UppyIcon uppy-Dashboard-poweredByIcon" width="11" height="11" viewBox="0 0 11 11">
     <path d="M7.365 10.5l-.01-4.045h2.612L5.5.806l-4.467 5.65h2.604l.01 4.044h3.718z" fill-rule="evenodd" />
   </svg><span class="uppy-Dashboard-poweredByUppy">Uppy</span></a>
 }

+ 16 - 10
packages/@uppy/dashboard/src/components/FileItem.js

@@ -15,14 +15,13 @@ function FileItemProgressWrapper (props) {
   }
 
   if (props.isUploaded ||
-      props.bundled ||
       (props.hidePauseResumeCancelButtons && !props.error)) {
     return <div class="uppy-DashboardItem-progressIndicator">
       <FileItemProgress
         progress={props.file.progress.percentage}
         fileID={props.file.id}
         hidePauseResumeCancelButtons={props.hidePauseResumeCancelButtons}
-        bundled={props.bundled}
+        individualCancellation={props.individualCancellation}
       />
     </div>
   }
@@ -38,13 +37,14 @@ function FileItemProgressWrapper (props) {
       : <FileItemProgress
         progress={props.file.progress.percentage}
         fileID={props.file.id}
+        individualCancellation={props.individualCancellation}
         hidePauseResumeCancelButtons={props.hidePauseResumeCancelButtons}
       />
     }
   </button>
 }
 
-module.exports = function fileItem (props) {
+module.exports = function FileItem (props) {
   const file = props.file
   const acquirers = props.acquirers
 
@@ -72,7 +72,7 @@ module.exports = function fileItem (props) {
 
     if (props.resumableUploads) {
       props.pauseUpload(file.id)
-    } else {
+    } else if (props.individualCancellation) {
       props.cancelUpload(file.id)
     }
   }
@@ -91,9 +91,11 @@ module.exports = function fileItem (props) {
         return props.i18n('resumeUpload')
       }
       return props.i18n('pauseUpload')
-    } else {
+    } else if (props.individualCancellation) {
       return props.i18n('cancelUpload')
     }
+
+    return ''
   }
 
   const dashboardItemClass = classNames(
@@ -104,9 +106,13 @@ module.exports = function fileItem (props) {
     { 'is-paused': isPaused },
     { 'is-error': error },
     { 'is-resumable': props.resumableUploads },
-    { 'is-bundled': props.bundledUpload }
+    { 'is-noIndividualCancellation': !props.individualCancellation }
   )
 
+  const showRemoveButton = props.individualCancellation
+    ? !isUploaded
+    : !uploadInProgress && !isUploaded
+
   return <li class={dashboardItemClass} id={`uppy_${file.id}`} title={file.meta.name}>
     <div class="uppy-DashboardItem-preview">
       <div class="uppy-DashboardItem-previewInnerWrap" style={{ backgroundColor: getFileTypeIcon(file.type).color }}>
@@ -174,15 +180,15 @@ module.exports = function fileItem (props) {
       </div>
     </div>
     <div class="uppy-DashboardItem-action">
-      {!isUploaded &&
+      {showRemoveButton &&
         <button class="uppy-DashboardItem-remove"
           type="button"
           aria-label={props.i18n('removeFile')}
           title={props.i18n('removeFile')}
           onclick={() => props.removeFile(file.id)}>
-          <svg aria-hidden="true" class="UppyIcon" width="60" height="60" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg">
-            <path stroke="#FFF" stroke-width="1" fill-rule="nonzero" vector-effect="non-scaling-stroke" d="M30 1C14 1 1 14 1 30s13 29 29 29 29-13 29-29S46 1 30 1z" />
-            <path fill="#FFF" vector-effect="non-scaling-stroke" d="M42 39.667L39.667 42 30 32.333 20.333 42 18 39.667 27.667 30 18 20.333 20.333 18 30 27.667 39.667 18 42 20.333 32.333 30z" />
+          <svg aria-hidden="true" class="UppyIcon" width="18" height="18" viewBox="0 0 18 18">
+            <path d="M9 0C4.034 0 0 4.034 0 9s4.034 9 9 9 9-4.034 9-9-4.034-9-9-9z" />
+            <path fill="#FFF" d="M13 12.222l-.778.778L9 9.778 5.778 13 5 12.222 8.222 9 5 5.778 5.778 5 9 8.222 12.222 5l.778.778L9.778 9z" />
           </svg>
         </button>
       }

+ 1 - 1
packages/@uppy/dashboard/src/components/FileItemProgress.js

@@ -19,7 +19,7 @@ module.exports = (props) => {
           stroke-dashoffset={circleLength - (circleLength / 100 * props.progress)}
         />
       </g>
-      {!props.hidePauseResumeCancelButtons && !props.bundled ? (
+      {!props.hidePauseResumeCancelButtons ? (
         <g>
           <polygon class="play" transform="translate(3, 3)" points="12 20 12 10 20 15" />
           <g class="pause" transform="translate(14.5, 13)">

+ 1 - 1
packages/@uppy/dashboard/src/components/FilePreview.js

@@ -13,7 +13,7 @@ module.exports = function FilePreview (props) {
   return (
     <div class="uppy-DashboardItem-previewIconWrap">
       <span class="uppy-DashboardItem-previewIcon" style={{ color: color }}>{icon}</span>
-      <svg class="uppy-DashboardItem-previewIconBg" width="72" height="93" viewBox="0 0 72 93"><g><path d="M24.08 5h38.922A2.997 2.997 0 0 1 66 8.003v74.994A2.997 2.997 0 0 1 63.004 86H8.996A2.998 2.998 0 0 1 6 83.01V22.234L24.08 5z" fill="#FFF" /><path d="M24 5L6 22.248h15.007A2.995 2.995 0 0 0 24 19.244V5z" fill="#E4E4E4" /></g></svg>
+      <svg class="uppy-DashboardItem-previewIconBg" width="58" height="76" viewBox="0 0 58 76"><rect fill="#FFF" width="58" height="76" rx="3" fill-rule="evenodd" /></svg>
     </div>
   )
 }

+ 2 - 2
packages/@uppy/dashboard/src/components/PickerPanelTopBar.js

@@ -94,8 +94,8 @@ function PanelTopBar (props) {
           aria-label={props.i18n('addMoreFiles')}
           title={props.i18n('addMoreFiles')}
           onclick={() => props.toggleAddFilesPanel(true)}>
-          <svg class="UppyIcon" width="15" height="15" viewBox="0 0 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg">
-            <path d="M7,6 L13,6 L13,7 L7,7 L7,13 L6,13 L6,7 L0,7 L0,6 L6,6 L6,0 L7,0 L7,6 Z" />
+          <svg class="UppyIcon" width="15" height="15" viewBox="0 0 15 15">
+            <path d="M8 6.5h6a.5.5 0 0 1 .5.5v.5a.5.5 0 0 1-.5.5H8v6a.5.5 0 0 1-.5.5H7a.5.5 0 0 1-.5-.5V8h-6a.5.5 0 0 1-.5-.5V7a.5.5 0 0 1 .5-.5h6v-6A.5.5 0 0 1 7 0h.5a.5.5 0 0 1 .5.5v6z" />
           </svg>
         </button>
       }

+ 14 - 11
packages/@uppy/dashboard/src/components/icons.js

@@ -38,7 +38,7 @@ function localIcon () {
 }
 
 function iconRetry () {
-  return <svg aria-hidden="true" class="UppyIcon retry" width="28" height="31" viewBox="0 0 16 19" xmlns="http://www.w3.org/2000/svg">
+  return <svg aria-hidden="true" class="UppyIcon retry" width="28" height="31" viewBox="0 0 16 19">
     <path d="M16 11a8 8 0 1 1-8-8v2a6 6 0 1 0 6 6h2z" />
     <path d="M7.9 3H10v2H7.9z" />
     <path d="M8.536.5l3.535 3.536-1.414 1.414L7.12 1.914z" />
@@ -53,32 +53,35 @@ function checkIcon () {
 }
 
 function iconAudio () {
-  return <svg aria-hidden="true" class="UppyIcon" width="55" height="55" viewBox="0 0 55 55">
-    <path d="M52.66.25c-.216-.19-.5-.276-.79-.242l-31 4.01a1 1 0 0 0-.87.992V40.622C18.174 38.428 15.273 37 12 37c-5.514 0-10 4.037-10 9s4.486 9 10 9 10-4.037 10-9c0-.232-.02-.46-.04-.687.014-.065.04-.124.04-.192V16.12l29-3.753v18.257C49.174 28.428 46.273 27 43 27c-5.514 0-10 4.037-10 9s4.486 9 10 9c5.464 0 9.913-3.966 9.993-8.867 0-.013.007-.024.007-.037V1a.998.998 0 0 0-.34-.75zM12 53c-4.41 0-8-3.14-8-7s3.59-7 8-7 8 3.14 8 7-3.59 7-8 7zm31-10c-4.41 0-8-3.14-8-7s3.59-7 8-7 8 3.14 8 7-3.59 7-8 7zM22 14.1V5.89l29-3.753v8.21l-29 3.754z" />
+  return <svg aria-hidden="true" class="UppyIcon" width="25" height="25" viewBox="0 0 25 25">
+    <path d="M9.5 18.64c0 1.14-1.145 2-2.5 2s-2.5-.86-2.5-2c0-1.14 1.145-2 2.5-2 .557 0 1.079.145 1.5.396V7.25a.5.5 0 0 1 .379-.485l9-2.25A.5.5 0 0 1 18.5 5v11.64c0 1.14-1.145 2-2.5 2s-2.5-.86-2.5-2c0-1.14 1.145-2 2.5-2 .557 0 1.079.145 1.5.396V8.67l-8 2v7.97zm8-11v-2l-8 2v2l8-2zM7 19.64c.855 0 1.5-.484 1.5-1s-.645-1-1.5-1-1.5.484-1.5 1 .645 1 1.5 1zm9-2c.855 0 1.5-.484 1.5-1s-.645-1-1.5-1-1.5.484-1.5 1 .645 1 1.5 1z" fill="#049BCF" fill-rule="nonzero" />
   </svg>
 }
 
 function iconVideo () {
-  return <svg aria-hidden="true" class="UppyIcon" viewBox="0 0 58 58">
-    <path d="M36.537 28.156l-11-7a1.005 1.005 0 0 0-1.02-.033C24.2 21.3 24 21.635 24 22v14a1 1 0 0 0 1.537.844l11-7a1.002 1.002 0 0 0 0-1.688zM26 34.18V23.82L34.137 29 26 34.18z" /><path d="M57 6H1a1 1 0 0 0-1 1v44a1 1 0 0 0 1 1h56a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1zM10 28H2v-9h8v9zm-8 2h8v9H2v-9zm10 10V8h34v42H12V40zm44-12h-8v-9h8v9zm-8 2h8v9h-8v-9zm8-22v9h-8V8h8zM2 8h8v9H2V8zm0 42v-9h8v9H2zm54 0h-8v-9h8v9z" />
+  return <svg aria-hidden="true" class="UppyIcon" width="25" height="25" viewBox="0 0 25 25">
+    <path d="M16 11.834l4.486-2.691A1 1 0 0 1 22 10v6a1 1 0 0 1-1.514.857L16 14.167V17a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2.834zM15 9H5v8h10V9zm1 4l5 3v-6l-5 3z" fill="#19AF67" fill-rule="nonzero" />
   </svg>
 }
 
 function iconPDF () {
-  return <svg aria-hidden="true" class="UppyIcon" viewBox="0 0 342 335">
-    <path d="M329.337 227.84c-2.1 1.3-8.1 2.1-11.9 2.1-12.4 0-27.6-5.7-49.1-14.9 8.3-.6 15.8-.9 22.6-.9 12.4 0 16 0 28.2 3.1 12.1 3 12.2 9.3 10.2 10.6zm-215.1 1.9c4.8-8.4 9.7-17.3 14.7-26.8 12.2-23.1 20-41.3 25.7-56.2 11.5 20.9 25.8 38.6 42.5 52.8 2.1 1.8 4.3 3.5 6.7 5.3-34.1 6.8-63.6 15-89.6 24.9zm39.8-218.9c6.8 0 10.7 17.06 11 33.16.3 16-3.4 27.2-8.1 35.6-3.9-12.4-5.7-31.8-5.7-44.5 0 0-.3-24.26 2.8-24.26zm-133.4 307.2c3.9-10.5 19.1-31.3 41.6-49.8 1.4-1.1 4.9-4.4 8.1-7.4-23.5 37.6-39.3 52.5-49.7 57.2zm315.2-112.3c-6.8-6.7-22-10.2-45-10.5-15.6-.2-34.3 1.2-54.1 3.9-8.8-5.1-17.9-10.6-25.1-17.3-19.2-18-35.2-42.9-45.2-70.3.6-2.6 1.2-4.8 1.7-7.1 0 0 10.8-61.5 7.9-82.3-.4-2.9-.6-3.7-1.4-5.9l-.9-2.5c-2.9-6.76-8.7-13.96-17.8-13.57l-5.3-.17h-.1c-10.1 0-18.4 5.17-20.5 12.84-6.6 24.3.2 60.5 12.5 107.4l-3.2 7.7c-8.8 21.4-19.8 43-29.5 62l-1.3 2.5c-10.2 20-19.5 37-27.9 51.4l-8.7 4.6c-.6.4-15.5 8.2-19 10.3-29.6 17.7-49.28 37.8-52.54 53.8-1.04 5-.26 11.5 5.01 14.6l8.4 4.2c3.63 1.8 7.53 2.7 11.43 2.7 21.1 0 45.6-26.2 79.3-85.1 39-12.7 83.4-23.3 122.3-29.1 29.6 16.7 66 28.3 89 28.3 4.1 0 7.6-.4 10.5-1.2 4.4-1.1 8.1-3.6 10.4-7.1 4.4-6.7 5.4-15.9 4.1-25.4-.3-2.8-2.6-6.3-5-8.7z" />
+  return <svg aria-hidden="true" class="UppyIcon" width="25" height="25" viewBox="0 0 25 25">
+    <path d="M9.766 8.295c-.691-1.843-.539-3.401.747-3.726 1.643-.414 2.505.938 2.39 3.299-.039.79-.194 1.662-.537 3.148.324.49.66.967 1.055 1.51.17.231.382.488.629.757 1.866-.128 3.653.114 4.918.655 1.487.635 2.192 1.685 1.614 2.84-.566 1.133-1.839 1.084-3.416.249-1.141-.604-2.457-1.634-3.51-2.707a13.467 13.467 0 0 0-2.238.426c-1.392 4.051-4.534 6.453-5.707 4.572-.986-1.58 1.38-4.206 4.914-5.375.097-.322.185-.656.264-1.001.08-.353.306-1.31.407-1.737-.678-1.059-1.2-2.031-1.53-2.91zm2.098 4.87c-.033.144-.068.287-.104.427l.033-.01-.012.038a14.065 14.065 0 0 1 1.02-.197l-.032-.033.052-.004a7.902 7.902 0 0 1-.208-.271c-.197-.27-.38-.526-.555-.775l-.006.028-.002-.003c-.076.323-.148.632-.186.8zm5.77 2.978c1.143.605 1.832.632 2.054.187.26-.519-.087-1.034-1.113-1.473-.911-.39-2.175-.608-3.55-.608.845.766 1.787 1.459 2.609 1.894zM6.559 18.789c.14.223.693.16 1.425-.413.827-.648 1.61-1.747 2.208-3.206-2.563 1.064-4.102 2.867-3.633 3.62zm5.345-10.97c.088-1.793-.351-2.48-1.146-2.28-.473.119-.564 1.05-.056 2.405.213.566.52 1.188.908 1.859.18-.858.268-1.453.294-1.984z" fill="#E2514A" fill-rule="nonzero" />
   </svg>
 }
 
 function iconFile () {
-  return <svg aria-hidden="true" class="UppyIcon" width="44" height="58" viewBox="0 0 44 58">
-    <path d="M27.437.517a1 1 0 0 0-.094.03H4.25C2.037.548.217 2.368.217 4.58v48.405c0 2.212 1.82 4.03 4.03 4.03H39.03c2.21 0 4.03-1.818 4.03-4.03V15.61a1 1 0 0 0-.03-.28 1 1 0 0 0 0-.093 1 1 0 0 0-.03-.032 1 1 0 0 0 0-.03 1 1 0 0 0-.032-.063 1 1 0 0 0-.03-.063 1 1 0 0 0-.032 0 1 1 0 0 0-.03-.063 1 1 0 0 0-.032-.03 1 1 0 0 0-.03-.063 1 1 0 0 0-.063-.062l-14.593-14a1 1 0 0 0-.062-.062A1 1 0 0 0 28 .708a1 1 0 0 0-.374-.157 1 1 0 0 0-.156 0 1 1 0 0 0-.03-.03l-.003-.003zM4.25 2.547h22.218v9.97c0 2.21 1.82 4.03 4.03 4.03h10.564v36.438a2.02 2.02 0 0 1-2.032 2.032H4.25c-1.13 0-2.032-.9-2.032-2.032V4.58c0-1.13.902-2.032 2.03-2.032zm24.218 1.345l10.375 9.937.75.718H30.5c-1.13 0-2.032-.9-2.032-2.03V3.89z" />
+  return <svg aria-hidden="true" class="UppyIcon" width="25" height="25" viewBox="0 0 25 25">
+    <g fill="#A7AFB7" fill-rule="nonzero">
+      <path d="M5.5 22a.5.5 0 0 1-.5-.5v-18a.5.5 0 0 1 .5-.5h10.719a.5.5 0 0 1 .367.16l3.281 3.556a.5.5 0 0 1 .133.339V21.5a.5.5 0 0 1-.5.5h-14zm.5-1h13V7.25L16 4H6v17z" />
+      <path d="M15 4v3a1 1 0 0 0 1 1h3V7h-3V4h-1z" />
+    </g>
   </svg>
 }
 
 function iconText () {
-  return <svg aria-hidden="true" class="UppyIcon" width="62" height="62" viewBox="0 0 62 62" xmlns="http://www.w3.org/2000/svg">
-    <path d="M4.309 4.309h24.912v53.382h-6.525v3.559h16.608v-3.559h-6.525V4.309h24.912v10.676h3.559V.75H.75v14.235h3.559z" fill-rule="nonzero" fill="#000" />
+  return <svg aria-hidden="true" class="UppyIcon" width="25" height="25" viewBox="0 0 25 25">
+    <path d="M4.5 7h13a.5.5 0 1 1 0 1h-13a.5.5 0 0 1 0-1zm0 3h15a.5.5 0 1 1 0 1h-15a.5.5 0 1 1 0-1zm0 3h15a.5.5 0 1 1 0 1h-15a.5.5 0 1 1 0-1zm0 3h10a.5.5 0 1 1 0 1h-10a.5.5 0 1 1 0-1z" fill="#5A5E69" fill-rule="nonzero" />
   </svg>
 }
 

+ 11 - 22
packages/@uppy/dashboard/src/index.js

@@ -51,11 +51,9 @@ module.exports = class Dashboard extends Plugin {
     this.type = 'orchestrator'
     this.modalName = `uppy-Dashboard-${cuid()}`
 
-    const defaultLocale = {
+    this.defaultLocale = {
       strings: {
-        selectToUpload: 'Select files to upload',
         closeModal: 'Close Modal',
-        upload: 'Upload',
         importFrom: 'Import from %{name}',
         addingMoreFiles: 'Adding more files',
         addMoreFiles: 'Add more files',
@@ -68,7 +66,6 @@ module.exports = class Dashboard extends Plugin {
         fileSource: 'File source: %{name}',
         done: 'Done',
         back: 'Back',
-        name: 'Name',
         removeFile: 'Remove file',
         editFile: 'Edit file',
         editing: 'Editing %{file}',
@@ -76,14 +73,10 @@ module.exports = class Dashboard extends Plugin {
         finishEditingFile: 'Finish editing file',
         saveChanges: 'Save changes',
         cancel: 'Cancel',
-        localDisk: 'Local Disk',
         myDevice: 'My Device',
         dropPasteImport: 'Drop files here, paste, %{browse} or import from',
         dropPaste: 'Drop files here, paste or %{browse}',
         browse: 'browse',
-        fileProgress: 'File progress: upload speed and ETA',
-        numberOfSelectedFiles: 'Number of selected files',
-        uploadAllNewFiles: 'Upload all new files',
         emptyFolderAdded: 'No files were added from empty folder',
         uploadComplete: 'Upload complete',
         uploadPaused: 'Upload paused',
@@ -93,27 +86,23 @@ module.exports = class Dashboard extends Plugin {
         cancelUpload: 'Cancel upload',
         xFilesSelected: {
           0: '%{smart_count} file selected',
-          1: '%{smart_count} files selected'
-        },
-        uploadXFiles: {
-          0: 'Upload %{smart_count} file',
-          1: 'Upload %{smart_count} files'
+          1: '%{smart_count} files selected',
+          2: '%{smart_count} files selected'
         },
         uploadingXFiles: {
           0: 'Uploading %{smart_count} file',
-          1: 'Uploading %{smart_count} files'
+          1: 'Uploading %{smart_count} files',
+          2: 'Uploading %{smart_count} files'
         },
         processingXFiles: {
           0: 'Processing %{smart_count} file',
-          1: 'Processing %{smart_count} files'
-        },
-        uploadXNewFiles: {
-          0: 'Upload +%{smart_count} file',
-          1: 'Upload +%{smart_count} files'
+          1: 'Processing %{smart_count} files',
+          2: 'Processing %{smart_count} files'
         },
         folderAdded: {
           0: 'Added %{smart_count} file from %{folder}',
-          1: 'Added %{smart_count} files from %{folder}'
+          1: 'Added %{smart_count} files from %{folder}',
+          2: 'Added %{smart_count} files from %{folder}'
         }
       }
     }
@@ -152,7 +141,7 @@ module.exports = class Dashboard extends Plugin {
     this.opts = { ...defaultOptions, ...opts }
 
     // i18n
-    this.translator = new Translator([ defaultLocale, this.uppy.locale, this.opts.locale ])
+    this.translator = new Translator([ this.defaultLocale, this.uppy.locale, this.opts.locale ])
     this.i18n = this.translator.translate.bind(this.translator)
     this.i18nArray = this.translator.translateArray.bind(this.translator)
 
@@ -729,7 +718,7 @@ module.exports = class Dashboard extends Plugin {
       note: this.opts.note,
       metaFields: pluginState.metaFields,
       resumableUploads: capabilities.resumableUploads || false,
-      bundled: capabilities.bundled || false,
+      individualCancellation: capabilities.individualCancellation,
       startUpload,
       pauseUpload: this.uppy.pauseResume,
       retryUpload: this.uppy.retryUpload,

+ 2 - 2
packages/@uppy/dashboard/src/index.test.js

@@ -35,7 +35,7 @@ describe('Dashboard', () => {
         inline: true,
         target: 'body'
       })
-      core.use(GoogleDrivePlugin, { target: DashboardPlugin, serverUrl: 'https://fake.uppy.io/' })
+      core.use(GoogleDrivePlugin, { target: DashboardPlugin, companionUrl: 'https://fake.uppy.io/' })
     }).not.toThrow()
 
     core.close()
@@ -43,7 +43,7 @@ describe('Dashboard', () => {
 
   it('works when passing plugins in `plugins` array', () => {
     const core = new Core()
-    core.use(GoogleDrivePlugin, { serverUrl: 'https://fake.uppy.io/' })
+    core.use(GoogleDrivePlugin, { companionUrl: 'https://fake.uppy.io/' })
 
     expect(() => {
       core.use(DashboardPlugin, {

+ 184 - 147
packages/@uppy/dashboard/src/style.scss

@@ -106,20 +106,20 @@
   left: 0;
   right: 0;
   bottom: 0;
-  background-color: rgba($color-black, 0.5);
+  background-color: rgba($black, 0.5);
   z-index: $zIndex-2;
 }
 
 .uppy-Dashboard-inner {
   position: relative;
-  background-color: $color-almost-white;
+  background-color: $gray-50;
   max-width: 100%; /* no !important */
   max-height: 100%; /* no !important */
   // min-width: 290px;
   // min-height: 450px is required for everything to fit on mobile
   min-height: 450px;
   outline: none;
-  border: 1px solid rgba($color-gray, 0.2);
+  border: 1px solid $gray-100;
   border-radius: 5px;
 
   .uppy-size--md & {
@@ -162,7 +162,7 @@
     top: 50%;
     left: 50%;
     transform: translate(-50%, -50%);
-    box-shadow: 0 5px 15px 4px rgba($color-black, 0.15);
+    box-shadow: 0 5px 15px 4px rgba($black, 0.15);
   }
 }
 
@@ -172,7 +172,7 @@
   top: -33px;
   right: -2px;
   cursor: pointer;
-  color: rgba($color-white, 0.9);
+  color: rgba($white, 0.9);
   font-size: 27px;
   z-index: $zIndex-5;
 
@@ -192,6 +192,13 @@
   position: relative;
   text-align: center;
   flex: 1;
+  margin: 8px;
+  border: 1px dashed $gray-300;
+  border-radius: 3px;
+
+  .uppy-Dashboard-AddFilesPanel & {
+    border: none;
+  }
 }
 
 .uppy-DashboardTabs {
@@ -206,16 +213,16 @@
 }
 
 .uppy-DashboardTabs-title {
-  font-size: 15px;
+  font-size: 14px;
   line-height: 30px;
   font-weight: 400;
   margin: 0;
   padding: 0;
   text-align: center;
-  color: $color-asphalt-gray;
+  color: $gray-700;
 
   .uppy-size--md & {
-    font-size: 17px;
+    font-size: 16px;
     line-height: 40px;
   }
 }
@@ -237,12 +244,16 @@
 .uppy-Dashboard-browse {
   @include reset-button;
   cursor: pointer;
-  color: rgba($color-cornflower-blue, 0.9);
+  color: rgba($blue, 0.9);
+
+  &:hover {
+    text-decoration: underline;
+  }
 }
 
   .uppy-Dashboard-browse:focus {
     outline: none;
-    border-bottom: 2px solid $color-cornflower-blue;
+    border-bottom: 2px solid $blue;
   }
 
 .uppy-DashboardTabs-list {
@@ -260,7 +271,7 @@
     justify-content: center;
     max-width: 600px;
     overflow-x: initial;
-    margin-top: 30px;
+    margin-top: 15px;
     padding-top: 0;
   }
 }
@@ -269,13 +280,13 @@
   width: 100%;
   display: inline-block;
   text-align: center;
-  border-bottom: 1px solid rgba($color-gray, 0.2);
+  border-bottom: 1px solid $gray-200;
   padding: 0px 2px;
 
   .uppy-size--md & {
     width: initial;
     margin-bottom: 20px;
-    border-bottom: initial;
+    border-bottom: none;
     padding: 0;
   }
 }
@@ -288,30 +299,35 @@
   background-color: transparent;
   -webkit-appearance: none;
   appearance: none;
-  color: darken($color-gray, 25%);
+  color: $gray-700;
   display: flex;
   flex-direction: row;
   align-items: center;
   padding: 12px 20px;
   line-height: 1;
+  text-align: center;
 
   .uppy-size--md & {
-    width: 80px;
-    margin: 0 5px;
+    width: 86px;
+    margin-right: 1px;
     flex-direction: column;
-    padding: 0;
+    padding: 10px 3px;
+    border-radius: 5px;
   }
 }
 
+  .uppy-DashboardTab-btn::-moz-focus-inner {
+    border: 0;
+  }
+
   .uppy-DashboardTab-btn:hover {
-    color: $color-cornflower-blue;
+    background-color: $gray-100--highlighted;
   }
 
+  .uppy-DashboardTab-btn:active,
   .uppy-DashboardTab-btn:focus {
-    outline-offset: 0;
-    .uppy-size--md & {
-      outline-offset: $size-focus-offset;
-    }
+    background-color: darken($gray-100--highlighted, 2%);
+    outline: none;
   }
 
   .uppy-DashboardTab-btn svg {
@@ -331,10 +347,6 @@
     transition: transform ease-in-out .15s;
   }
 
-  .uppy-DashboardTab-btn:hover svg {
-    transform: translateZ(0) scale(1.1, 1.1);
-  }
-
 .uppy-DashboardTab-name {
   font-size: 14px;
   font-weight: 500;
@@ -374,9 +386,9 @@
   justify-content: space-between;
   height: 40px;
   width: 100%;
-  border-bottom: 1px solid rgba($color-gray, 0.3);
+  border-bottom: 1px solid $gray-200;
   z-index: $zIndex-4;
-  background-color: $color-almost-white;
+  background-color: $gray-50;
   padding: 0 10px;
 
   .uppy-size--md & {
@@ -393,7 +405,7 @@
   text-align: center;
   font-size: 12px;
   line-height: 40px;
-  font-weight: normal;
+  font-weight: 500;
   max-width: 170px;
   text-overflow: ellipsis;
   white-space: nowrap;
@@ -409,29 +421,38 @@
 
 .uppy-DashboardContent-back {
   @include reset-button;
-  font-size: 13px;
-  font-weight: 500;
+  display: inline-block;
+  font-size: 12px;
+  font-weight: 400;
   cursor: pointer;
-  color: $color-cornflower-blue;
+  color: $blue;
+  padding: 7px 6px;
+  margin-left: -6px;
+  border-radius: 3px;
+
+  &:hover {
+    color: darken($blue, 12%);
+  }
 
   .uppy-size--md & {
-    font-size: 15px;
+    font-size: 14px;
   }
 }
 
 .uppy-DashboardContent-addMore {
   @include reset-button;
+  display: inline-block;
   font-weight: 500;
   cursor: pointer;
-  color: $color-cornflower-blue;
-  stroke: $color-cornflower-blue;
-  stroke-width: 0.7px;
-  width: 13px;
-  height: 13px;
+  color: $blue;
+  width: 27px;
+  height: 27px;
+  padding: 6px;
+  margin-right: -6px;
+  border-radius: 3px;
 
-  .uppy-size--md & {
-    width: 15px;
-    height: 15px;
+  &:hover {
+    color: darken($blue, 12%)
   }
 }
 
@@ -445,7 +466,7 @@
   bottom: 0;
   left: 0;
   right: 0;
-  background-color: darken($color-white, 4%);
+  background-color: darken($white, 4%);
   overflow: hidden;
   z-index: $zIndex-5;
   border-radius: 5px;
@@ -460,9 +481,9 @@
   bottom: 0;
   left: 0;
   right: 0;
-  background: $color-almost-white;
-  background: linear-gradient(0deg, $color-almost-white 35%, rgba($color-almost-white, 0.85) 100%);
-  box-shadow: 0 0 10px 5px rgba($color-black, 0.15);
+  background: $gray-50;
+  background: linear-gradient(0deg, $gray-50 35%, rgba($gray-50, 0.85) 100%);
+  box-shadow: 0 0 10px 5px rgba($black, 0.15);
   overflow: hidden;
   z-index: $zIndex-5;
   border-radius: 5px;
@@ -493,37 +514,37 @@
   height: 100%;
 }
 
-.uppy-Dashboard-next {
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background: none;
-  background-color: rgba($color-gray, 0.7);
-  -webkit-appearance: none;
-  appearance: none;
-  border: 0;
-  z-index: $zIndex-3;
-  transition: background-color 0.5s;
-  color: $color-white;
-  font-family: inherit;
-  font-size: 14px;
-  line-height: 1;
-  padding: 0;
-  margin: 0;
-  outline: none;
-  cursor: not-allowed;
-}
-
-.uppy-Dashboard-next.is-active {
-  cursor: pointer;
-  background-color: $color-cornflower-blue;
-
-  &:hover {
-    background-color: darken($color-cornflower-blue, 20%);
-  }
-}
+// .uppy-Dashboard-next {
+//   position: absolute;
+//   top: 0;
+//   left: 0;
+//   width: 100%;
+//   height: 100%;
+//   background: none;
+//   background-color: rgba($gray-500, 0.7);
+//   -webkit-appearance: none;
+//   appearance: none;
+//   border: 0;
+//   z-index: $zIndex-3;
+//   transition: background-color 0.5s;
+//   color: $white;
+//   font-family: inherit;
+//   font-size: 14px;
+//   line-height: 1;
+//   padding: 0;
+//   margin: 0;
+//   outline: none;
+//   cursor: not-allowed;
+// }
+
+// .uppy-Dashboard-next.is-active {
+//   cursor: pointer;
+//   background-color: $blue;
+
+//   &:hover {
+//     background-color: darken($blue, 20%);
+//   }
+// }
 
 .uppy-Dashboard-filesContainer {
   @include clearfix;
@@ -546,15 +567,15 @@
   }
 
 .uppy-Dashboard.drag .uppy-Dashboard-innerWrap {
-  background-color: darken($color-almost-white, 25%)
+  background-color: darken($gray-50, 25%)
 }
 
 .uppy-Dashboard.drag .uppy-Dashboard-AddFilesPanel {
-  background: darken($color-almost-white, 20%)
+  background: darken($gray-50, 20%)
 }
 
 .uppy-Dashboard.drag .uppy-Dashboard-files--noFiles {
-  border-color: darken($color-almost-white, 20%);
+  border-color: darken($gray-50, 20%);
 }
 
 .uppy-Dashboard-dropFilesTitle {
@@ -563,7 +584,7 @@
   font-size: 16px;
   line-height: 1.45;
   font-weight: 400;
-  color: $color-asphalt-gray;
+  color: $gray-700;
   margin: auto;
   padding: 0 15px;
   padding-top: 20px;
@@ -575,10 +596,10 @@
 }
 
 .uppy-Dashboard-note {
-  font-size: 13px;
+  font-size: 14px;
   line-height: 1.25;
   text-align: center;
-  color: rgba($color-asphalt-gray, 0.8);
+  color: $gray-600;
   max-width: 350px;
   margin: auto;
   padding: 0 15px;
@@ -594,13 +615,13 @@ a.uppy-Dashboard-poweredBy {
   display: inline-block;
   text-align: center;
   font-size: 11px;
-  color: $color-gray;
+  color: $gray-500;
   text-decoration: none;
   margin-top: 8px;
 }
 
 .uppy-Dashboard-poweredByIcon {
-  stroke: $color-gray;
+  stroke: $gray-500;
   fill: none;
   margin-left: 1px;
   margin-right: 1px;
@@ -616,7 +637,7 @@ a.uppy-Dashboard-poweredBy {
   position: relative;
   display: flex;
   align-items: center;
-  border-bottom: 1px solid lighten($color-gray, 35%);
+  border-bottom: 1px solid $gray-200;
   padding-bottom: 10px;
   padding-left: 10px;
 
@@ -681,10 +702,8 @@ a.uppy-Dashboard-poweredBy {
 
 .uppy-DashboardItem-sourceIcon {
   display: inline-block;
-  vertical-align: middle;
-  width: 11px;
-  height: 11px;
-  color: rgba($color-gray, 0.85);
+  vertical-align: bottom;
+  color: $gray-500;
 }
 
 .uppy-DashboardItem-sourceIcon svg,
@@ -692,9 +711,11 @@ a.uppy-Dashboard-poweredBy {
   max-width: 100%;
   max-height: 100%;
   display: inline-block;
-  vertical-align: text-top;
+  vertical-align: text-bottom;
   overflow: hidden;
   fill: currentColor;
+  width: 11px;
+  height: 12px;
 }
 
 .uppy-DashboardItem-previewInnerWrap {
@@ -706,11 +727,11 @@ a.uppy-Dashboard-poweredBy {
   justify-content: center;
   align-items: center;
   flex-direction: column;
-  box-shadow: 0 0 2px 0 rgba($color-gray, 0.7);
+  box-shadow: 0 0 2px 0 rgba($black, 0.4);
   border-radius: 3px;
 
   .uppy-size--md & {
-    box-shadow: 0 1px 3px rgba($color-black,.2);
+    box-shadow: 0 1px 3px rgba($black, 0.2);
   }
 }
 
@@ -721,7 +742,7 @@ a.uppy-Dashboard-poweredBy {
     bottom: 0;
     left: 0;
     right: 0;
-    background-color: rgba($color-black, 0.65) /* no !important */;
+    background-color: rgba($black, 0.65) /* no !important */;
     display: none;
     z-index: $zIndex-2;
   }
@@ -735,20 +756,20 @@ a.uppy-Dashboard-poweredBy {
 
 
 .uppy-DashboardItem-previewIconWrap {
-  height: 80px;
-  max-height: 90%;
+  height: 76px;
+  max-height: 75%;
   position: relative;
 }
 
 .uppy-DashboardItem-previewIconBg {
   width: 100%;
   height: 100%;
-  filter: drop-shadow(rgba($color-black, 0.1) 0px 0px 1px);
+  filter: drop-shadow(rgba($black, 0.1) 0px 1px 1px);
 }
 
 .uppy-DashboardItem-previewIcon {
-  width: 18px;
-  height: 18px;
+  width: 25px;
+  height: 25px;
   z-index: $zIndex-1;
   position: absolute;
   top: 50%;
@@ -756,8 +777,13 @@ a.uppy-Dashboard-poweredBy {
   transform: translate(-50%, -50%);
 
   .uppy-size--md & {
-    width: 25px;
-    height: 25px;
+    width: 38px;
+    height: 38px;
+  }
+
+  svg {
+    width: 100%;
+    height: 100%;
   }
 }
 
@@ -769,7 +795,7 @@ a.uppy-Dashboard-poweredBy {
   text-transform: uppercase;
   font-size: 9px;
   letter-spacing: 1px;
-  color: $color-asphalt-gray;
+  color: $gray-700;
   z-index: $zIndex-1;
   user-select: none;
 }
@@ -783,18 +809,17 @@ a.uppy-Dashboard-poweredBy {
     width: 100%;
     max-width: 100%;
     flex: 1;
-    padding: 8px 3px 0 3px;
+    padding: 8px 0 0;
     border-top: 0;
   }
 }
 
 .uppy-DashboardItem-name {
-  font-size: 11px;
-  line-height: 1.35;
+  font-size: 12px;
+  line-height: 1.3;
   font-weight: 500;
   margin: 0;
   padding: 0;
-  max-height: 28px;
   margin-bottom: 5px;
   text-overflow: ellipsis;
   white-space: nowrap;
@@ -808,15 +833,15 @@ a.uppy-Dashboard-poweredBy {
 }
 
 .uppy-DashboardItem-name a {
-  text-decoration: underline;
-  color: $color-black;
+  text-decoration: none;
+  color: $gray-800;
 }
 
 .uppy-DashboardItem-status {
   font-size: 11px;
-  line-height: 11px;
+  line-height: 1.3;
   font-weight: normal;
-  color: darken($color-gray, 15%);
+  color: $gray-600;
   margin-bottom: 4px;
 }
 
@@ -833,8 +858,12 @@ a.uppy-Dashboard-poweredBy {
   cursor: pointer;
   font-family: inherit;
   font-size: inherit;
-  line-height: 1;
+  line-height: inherit;
   color: inherit;
+
+  &:hover {
+    text-decoration: underline;
+  }
 }
 
 .uppy-DashboardItem-edit:not(:first-child),
@@ -849,8 +878,7 @@ a.uppy-Dashboard-poweredBy {
     position: absolute;
     top: 0;
     left: -9px;
-    color: $color-gray;
-    font-weight: 700;
+    color: $gray-600;
   }
 }
 
@@ -869,14 +897,14 @@ a.uppy-Dashboard-poweredBy {
 .uppy-DashboardItem-remove {
   @include reset-button;
   cursor: pointer;
-  color: $color-black;
-  width: 16px;
-  height: 16px;
-  opacity: 0.75;
+  color: $gray-900;
+  width: 20px;
+  height: 20px;
+  padding: 1px;
+  opacity: 0.9;
 
-  .uppy-size--md & {
-    width: 20px;
-    height: 20px;
+  &:hover {
+    opacity: 1;
   }
 }
 
@@ -891,7 +919,7 @@ a.uppy-Dashboard-poweredBy {
   left: 50%;
   transform: translate(-50%, -50%);
   z-index: $zIndex-3;
-  color: $color-white;
+  color: $white;
   text-align: center;
   width: 120px;
   display: none;
@@ -918,7 +946,6 @@ a.uppy-Dashboard-poweredBy {
   width: 38px;
   height: 38px;
   opacity: 0.9;
-  transition: all .35s ease;
 
   .uppy-size--md & {
     width: 55px;
@@ -946,8 +973,8 @@ a.uppy-Dashboard-poweredBy {
     opacity: 1;
 
     .uppy-size--md & {
-      width: 25px;
-      height: 25px;
+      width: 22px;
+      height: 22px;
     }
   }
 
@@ -961,7 +988,7 @@ a.uppy-Dashboard-poweredBy {
   bottom: -10px;
   left: 0;
   width: 100%;
-  text-shadow: 0 1px 0 rgba($color-black, 0.3);
+  text-shadow: 0 1px 0 rgba($black, 0.3);
 
   .uppy-size--md & {
     display: block;
@@ -974,40 +1001,40 @@ a.uppy-Dashboard-poweredBy {
 }
 
 .uppy-DashboardItem .bg {
-  stroke: rgba($color-white, 0.4);
+  stroke: rgba($white, 0.4);
   opacity: 0;
 }
 
 .uppy-DashboardItem .progress {
-  stroke: $color-white;
+  stroke: $white;
   transition: stroke-dashoffset .5s ease-out;
   opacity: 0;
 }
 
 .uppy-DashboardItem .play {
-  stroke: $color-white;
-  fill: $color-white;
+  stroke: $white;
+  fill: $white;
   opacity: 0;
   transition: all 0.2s;
   display: none;
 }
 
 .uppy-DashboardItem .cancel {
-  fill: $color-white;
+  fill: $white;
   opacity: 0;
   transition: all 0.2s;
 }
 
 .uppy-DashboardItem .pause {
-  stroke: $color-white;
-  fill: $color-white;
+  stroke: $white;
+  fill: $white;
   opacity: 0;
   transition: all 0.2s;
   display: none;
 }
 
 .uppy-DashboardItem.is-error .retry {
-  fill: $color-white;
+  fill: $white;
 }
 
 .uppy-DashboardItem.is-resumable {
@@ -1017,7 +1044,7 @@ a.uppy-Dashboard-poweredBy {
 
 .UppyIcon-progressCircle .check {
   opacity: 0;
-  fill: $color-white;
+  fill: $white;
   transition: all 0.2s;
 }
 
@@ -1046,6 +1073,16 @@ a.uppy-Dashboard-poweredBy {
   }
 }
 
+.uppy-DashboardItem.is-noIndividualCancellation {
+  .uppy-DashboardItem-progressIndicator {
+    cursor: default;
+  }
+
+  .cancel {
+    display: none;
+  }
+}
+
 .uppy-DashboardItem.is-processing .uppy-DashboardItem-progress {
   opacity: 0;
 }
@@ -1056,8 +1093,8 @@ a.uppy-Dashboard-poweredBy {
   }
 
   .progress {
-    stroke: $color-green;
-    fill: $color-green;
+    stroke: $green;
+    fill: $green;
     opacity: 1;
   }
 
@@ -1073,7 +1110,7 @@ a.uppy-Dashboard-poweredBy {
 
 .uppy-DashboardItem-progressInner {
   height: 15px;
-  background-color: $color-cornflower-blue;
+  background-color: $blue;
   position: absolute;
   top: 0;
   left: 0;
@@ -1081,11 +1118,11 @@ a.uppy-Dashboard-poweredBy {
 
 .uppy-Dashboard-actions {
   height: 55px;
-  border-top: 1px solid rgba($color-gray, 0.3);
+  border-top: 1px solid $gray-200;
   display: flex;
   align-items: center;
   padding: 0 15px;
-  background-color: $color-almost-white;
+  background-color: $gray-50;
 }
 
   .uppy-size--md .uppy-Dashboard-actions {
@@ -1122,8 +1159,8 @@ a.uppy-Dashboard-poweredBy {
   position: absolute;
   top: -12px;
   right: -12px;
-  background-color: $color-green;
-  color: $color-white;
+  background-color: $green;
+  color: $white;
   border-radius: 50%;
   width: 16px;
   height: 16px;
@@ -1153,8 +1190,8 @@ a.uppy-Dashboard-poweredBy {
   right: 0;
   bottom: 0;
   z-index: $zIndex-5;
-  box-shadow: 0px 0px 10px 4px rgba($color-black, 0.1);
-  background-color: $color-white;
+  box-shadow: 0px 0px 10px 4px rgba($black, 0.1);
+  background-color: $white;
   display: flex;
   flex-direction: column;
 }
@@ -1175,13 +1212,13 @@ a.uppy-Dashboard-poweredBy {
   align-items: center;
   justify-content: center;
   flex-grow: 1;
-  border-bottom: 1px solid rgba($color-gray, 0.3);
-  background-color: lighten($color-gray, 40%); /* no !important */
+  border-bottom: 1px solid $gray-200;
+  background-color: $gray-50; /* no !important */
   position: relative;
 }
 
 .uppy-DashboardFileCard-preview img {
-  box-shadow: 0px 3px 20px rgba($color-black, 0.15);
+  box-shadow: 0px 3px 20px rgba($black, 0.15);
   max-width: 90%;
   max-height: 90%;
   object-fit: cover;
@@ -1211,10 +1248,10 @@ a.uppy-Dashboard-poweredBy {
   vertical-align: middle;
   width: 22%;
   font-size: 12px;
-  color: $color-asphalt-gray;
+  color: $gray-700;
 
   .uppy-size--md & {
-    font-size: 13px;
+    font-size: 14px;
   }
 }
 

+ 7 - 7
packages/@uppy/dashboard/src/utils/getFileTypeIcon.js

@@ -1,9 +1,9 @@
-const { iconText, iconAudio, iconVideo, iconPDF } = require('../components/icons')
+const { iconFile, iconText, iconAudio, iconVideo, iconPDF } = require('../components/icons')
 
 module.exports = function getIconByMime (fileType) {
   const defaultChoice = {
-    color: '#cbcbcb',
-    icon: ''
+    color: '#838999',
+    icon: iconFile()
   }
 
   if (!fileType) return defaultChoice
@@ -13,28 +13,28 @@ module.exports = function getIconByMime (fileType) {
 
   if (fileTypeGeneral === 'text') {
     return {
-      color: '#cbcbcb',
+      color: '#5a5e69',
       icon: iconText()
     }
   }
 
   if (fileTypeGeneral === 'audio') {
     return {
-      color: '#1abc9c',
+      color: '#068dbb',
       icon: iconAudio()
     }
   }
 
   if (fileTypeGeneral === 'video') {
     return {
-      color: '#2980b9',
+      color: '#19af67',
       icon: iconVideo()
     }
   }
 
   if (fileTypeGeneral === 'application' && fileTypeSpecific === 'pdf') {
     return {
-      color: '#e74c3c',
+      color: '#e25149',
       icon: iconPDF()
     }
   }

+ 4 - 5
packages/@uppy/drag-drop/src/index.js

@@ -15,7 +15,7 @@ module.exports = class DragDrop extends Plugin {
     this.id = this.opts.id || 'DragDrop'
     this.title = 'Drag & Drop'
 
-    const defaultLocale = {
+    this.defaultLocale = {
       strings: {
         dropHereOr: 'Drop files here or %{browse}',
         browse: 'browse'
@@ -28,8 +28,7 @@ module.exports = class DragDrop extends Plugin {
       inputName: 'files[]',
       width: '100%',
       height: '100%',
-      note: null,
-      locale: defaultLocale
+      note: null
     }
 
     // Merge default options with the ones set by user
@@ -39,7 +38,7 @@ module.exports = class DragDrop extends Plugin {
     this.isDragDropSupported = this.checkDragDropSupport()
 
     // i18n
-    this.translator = new Translator([ defaultLocale, this.uppy.locale, this.opts.locale ])
+    this.translator = new Translator([ this.defaultLocale, this.uppy.locale, this.opts.locale ])
     this.i18n = this.translator.translate.bind(this.translator)
     this.i18nArray = this.translator.translateArray.bind(this.translator)
 
@@ -131,7 +130,7 @@ module.exports = class DragDrop extends Plugin {
     return (
       <div class={DragDropClass} style={DragDropStyle}>
         <div class="uppy-DragDrop-inner">
-          <svg aria-hidden="true" class="UppyIcon uppy-DragDrop-arrow" width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+          <svg aria-hidden="true" class="UppyIcon uppy-DragDrop-arrow" width="16" height="16" viewBox="0 0 16 16">
             <path d="M11 10V0H5v10H2l6 6 6-6h-3zm0 0" fill-rule="evenodd" />
           </svg>
           <label class="uppy-DragDrop-label">

+ 8 - 8
packages/@uppy/drag-drop/src/style.scss

@@ -6,7 +6,7 @@
   align-items: center;
   justify-content: center;
   border-radius: 7px;
-  background-color: $color-white;
+  background-color: $white;
   // cursor: pointer;
 }
 
@@ -20,13 +20,13 @@
 .uppy-DragDrop-arrow {
   width: 60px;
   height: 60px;
-  fill: lighten($color-gray, 30%);
+  fill: lighten($gray-500, 30%);
   margin-bottom: 17px;
 }
 
   .uppy-DragDrop--is-dragdrop-supported {
     border: 2px dashed;
-    border-color: lighten($color-gray, 10%);
+    border-color: lighten($gray-500, 10%);
   }
 
   // .uppy-DragDrop-container.is-dragdrop-supported .uppy-DragDrop-dragText {
@@ -34,12 +34,12 @@
   // }
 
   .uppy-DragDrop-container.drag {
-    border-color: $color-gray;
-    background-color: darken($color-white, 10%);
+    border-color: $gray-500;
+    background-color: darken($white, 10%);
   }
 
   .uppy-DragDrop-container.drag .uppy-DragDrop-arrow {
-    fill: $color-gray;
+    fill: $gray-500;
   }
 
 .uppy-DragDrop-label {
@@ -51,9 +51,9 @@
 
 .uppy-DragDrop-note {
   font-size: 1em;
-  color: lighten($color-gray, 10%);
+  color: lighten($gray-500, 10%);
 }
 
 .uppy-DragDrop-dragText {
-  color: $color-cornflower-blue;
+  color: $blue;
 }

+ 3 - 5
packages/@uppy/dropbox/src/index.js

@@ -10,15 +10,13 @@ module.exports = class Dropbox extends Plugin {
     Provider.initPlugin(this, opts)
     this.title = this.opts.title || 'Dropbox'
     this.icon = () => (
-      <svg aria-hidden="true" fill="#0060ff" width="128" height="118" viewBox="0 0 128 118">
-        <path d="M38.145.777L1.108 24.96l25.608 20.507 37.344-23.06z" />
-        <path d="M1.108 65.975l37.037 24.183L64.06 68.525l-37.343-23.06zM64.06 68.525l25.917 21.633 37.036-24.183-25.61-20.51z" />
-        <path d="M127.014 24.96L89.977.776 64.06 22.407l37.345 23.06zM64.136 73.18l-25.99 21.567-11.122-7.262v8.142l37.112 22.256 37.114-22.256v-8.142l-11.12 7.262z" />
+      <svg aria-hidden="true" width="128" height="128" viewBox="0 0 128 128">
+        <path d="M31.997 11L64 31.825 31.997 52.651 0 31.825 31.997 11zM96 11l32 20.825-32 20.826-32-20.826L96 11zM0 73.476l31.997-20.825L64 73.476 31.997 94.302 0 73.476zm96-20.825l32 20.825-32 20.826-32-20.826 32-20.825zm-64.508 48.254l32.003-20.826 31.997 20.826-31.997 20.825-32.003-20.825z" fill="#0260FF" fill-rule="nonzero" />
       </svg>
     )
 
     this.provider = new Provider(uppy, {
-      serverUrl: this.opts.serverUrl,
+      companionUrl: this.opts.companionUrl,
       serverHeaders: this.opts.serverHeaders,
       storage: this.opts.storage,
       provider: 'dropbox',

+ 2 - 2
packages/@uppy/dropbox/types/index.d.ts

@@ -3,8 +3,8 @@ import CompanionClient = require('@uppy/companion-client');
 
 declare module Dropbox {
   interface DropboxOptions extends Uppy.PluginOptions, CompanionClient.ProviderOptions {
-    serverUrl: string;
-    serverPattern: string | RegExp | Array<string | RegExp>;
+    companionUrl: string;
+    companionAllowedHosts: string | RegExp | Array<string | RegExp>;
   }
 }
 

+ 3 - 4
packages/@uppy/file-input/src/index.js

@@ -10,7 +10,7 @@ module.exports = class FileInput extends Plugin {
     this.title = 'File Input'
     this.type = 'acquirer'
 
-    const defaultLocale = {
+    this.defaultLocale = {
       strings: {
         chooseFiles: 'Choose files'
       }
@@ -20,15 +20,14 @@ module.exports = class FileInput extends Plugin {
     const defaultOptions = {
       target: null,
       pretty: true,
-      inputName: 'files[]',
-      locale: defaultLocale
+      inputName: 'files[]'
     }
 
     // Merge default options with the ones set by user
     this.opts = Object.assign({}, defaultOptions, opts)
 
     // i18n
-    this.translator = new Translator([ defaultLocale, this.uppy.locale, this.opts.locale ])
+    this.translator = new Translator([ this.defaultLocale, this.uppy.locale, this.opts.locale ])
     this.i18n = this.translator.translate.bind(this.translator)
     this.i18nArray = this.translator.translateArray.bind(this.translator)
 

+ 4 - 4
packages/@uppy/file-input/src/style.scss

@@ -12,13 +12,13 @@
   font-size: 0.85em;
   // text-transform: uppercase;
   padding: 10px 15px;
-  color: darken($color-cornflower-blue, 20%);
-  border: 1px solid darken($color-cornflower-blue, 20%);
+  color: darken($blue, 20%);
+  border: 1px solid darken($blue, 20%);
   border-radius: 8px;
   cursor: pointer;
 
   &:hover {
-    background-color: darken($color-cornflower-blue, 20%);
-    color: $color-white;
+    background-color: darken($blue, 20%);
+    color: $white;
   }
 }

+ 2 - 2
packages/@uppy/google-drive/src/index.js

@@ -11,7 +11,7 @@ module.exports = class GoogleDrive extends Plugin {
     Provider.initPlugin(this, opts)
     this.title = this.opts.title || 'Google Drive'
     this.icon = () => (
-      <svg aria-hidden="true" width="18px" height="16px" viewBox="0 0 18 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
+      <svg aria-hidden="true" width="18px" height="16px" viewBox="0 0 18 16" version="1.1">
         <g fill-rule="evenodd">
           <polygon fill="#3089FC" points="6.32475 10.2 18 10.2 14.999625 15.3 3.324375 15.3" />
           <polygon fill="#00A85D" points="3.000375 15.3 0 10.2 5.83875 0.275974026 8.838 5.37597403 5.999625 10.2" />
@@ -21,7 +21,7 @@ module.exports = class GoogleDrive extends Plugin {
     )
 
     this.provider = new Provider(uppy, {
-      serverUrl: this.opts.serverUrl,
+      companionUrl: this.opts.companionUrl,
       serverHeaders: this.opts.serverHeaders,
       storage: this.opts.storage,
       provider: 'drive',

+ 2 - 2
packages/@uppy/google-drive/types/index.d.ts

@@ -3,8 +3,8 @@ import CompanionClient = require('@uppy/companion-client');
 
 declare module GoogleDrive {
   interface GoogleDriveOptions extends Uppy.PluginOptions, CompanionClient.ProviderOptions {
-    serverUrl: string;
-    serverPattern: string | RegExp | Array<string | RegExp>;
+    companionUrl: string;
+    companionAllowedHosts: string | RegExp | Array<string | RegExp>;
   }
 }
 

+ 4 - 4
packages/@uppy/informer/src/style.scss

@@ -46,8 +46,8 @@
   line-height: 1.4;
   font-weight: 400;
   padding: 6px 15px;
-  background-color: rgba($color-asphalt-gray, 0.8); /* no !important */
-  color: $color-white;
+  background-color: $gray-600; /* no !important */
+  color: $white;
   border-radius: 18px;
   max-width: 90%;
 
@@ -65,8 +65,8 @@
   height: 13px;
   display: inline-block;
   vertical-align: middle;
-  color: $color-asphalt-gray;
-  background-color: $color-white;
+  color: $gray-700;
+  background-color: $white;
   border-radius: 50%;
   position: relative;
   top: -1px;

+ 1 - 1
packages/@uppy/instagram/src/index.js

@@ -18,7 +18,7 @@ module.exports = class Instagram extends Plugin {
     )
 
     this.provider = new Provider(uppy, {
-      serverUrl: this.opts.serverUrl,
+      companionUrl: this.opts.companionUrl,
       serverHeaders: this.opts.serverHeaders,
       storage: this.opts.storage,
       provider: 'instagram',

+ 2 - 2
packages/@uppy/instagram/types/index.d.ts

@@ -3,8 +3,8 @@ import CompanionClient = require('@uppy/companion-client');
 
 declare module Instagram {
   interface InstagramOptions extends Uppy.PluginOptions, CompanionClient.ProviderOptions {
-    serverUrl: string;
-    serverPattern: string | RegExp | Array<string | RegExp>;
+    companionUrl: string;
+    companionAllowedHosts: string | RegExp | Array<string | RegExp>;
   }
 }
 

+ 969 - 0
packages/@uppy/locales/legacy/README.md

@@ -0,0 +1,969 @@
+Dear translators! 
+
+We tried to salvage your locales from when we had to remove them [10 months ago](https://github.com/transloadit/uppy/tree/10b19ea79b1fb728d97af89e74edca9d03208a75/locales). Unfortunately, a lot has changed since them, which made them outdated. Here are the issues we found, comparing them to en_US (which is the leading locale).
+
+Any help to restore these locales is greatly appreciated <3
+
+- [ ] cs_CZ locale has missing string: 'addMoreFiles' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'addMoreFiles' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'addingMoreFiles' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'allowAccessDescription' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'allowAccessTitle' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'back' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'cancel' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'cancelUpload' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'chooseFiles' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'companionAuthError' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'companionError' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'complete' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'connectedToInternet' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'copyLink' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'creatingAssembly' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'creatingAssemblyFailed' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'dataUploadedOfTotal' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'dropHereOr' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'edit' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'editFile' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'editing' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'emptyFolderAdded' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'encoding' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'enterCorrectUrl' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'enterUrlToImport' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'exceedsSize' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'failedToFetch' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'failedToUpload' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'fileSource' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'filesUploadedOfTotal.0' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'filesUploadedOfTotal.1' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'filesUploadedOfTotal.2' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'filter' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'finishEditingFile' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'folderAdded.0' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'folderAdded.1' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'folderAdded.2' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'import' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'link' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'logOut' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'myDevice' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'noFilesFound' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'noInternetConnection' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'pause' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'pauseUpload' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'paused' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'preparingUpload' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'processingXFiles.0' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'processingXFiles.1' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'processingXFiles.2' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'removeFile' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'resetFilter' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'resume' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'resumeUpload' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'retry' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'retryUpload' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'saveChanges' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'selectXFiles.0' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'selectXFiles.1' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'selectXFiles.2' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'smile' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'startRecording' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'stopRecording' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'takePicture' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'timedOut' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'uploadComplete' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'uploadFailed' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'uploadPaused' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'uploadXFiles.0' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'uploadXFiles.1' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'uploadXFiles.2' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'uploadXNewFiles.0' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'uploadXNewFiles.1' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'uploadXNewFiles.2' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'uploading' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'uploadingXFiles.0' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'uploadingXFiles.1' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'uploadingXFiles.2' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'xFilesSelected.0' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'xFilesSelected.1' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'xFilesSelected.2' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'xMoreFilesAdded.0' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'xMoreFilesAdded.1' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'xMoreFilesAdded.2' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'xTimeLeft' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'youCanOnlyUploadFileTypes' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'youCanOnlyUploadX.0' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'youCanOnlyUploadX.1' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'youCanOnlyUploadX.2' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'youHaveToAtLeastSelectX.0' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'youHaveToAtLeastSelectX.1' that is present in en_US. 
+- [ ] cs_CZ locale has missing string: 'youHaveToAtLeastSelectX.2' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'addMoreFiles' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'addingMoreFiles' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'allowAccessDescription' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'allowAccessTitle' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'back' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'cancel' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'cancelUpload' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'chooseFiles' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'companionAuthError' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'companionError' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'complete' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'connectedToInternet' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'copyLink' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'creatingAssembly' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'creatingAssemblyFailed' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'dataUploadedOfTotal' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'dropHereOr' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'edit' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'editFile' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'editing' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'emptyFolderAdded' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'encoding' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'enterCorrectUrl' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'enterUrlToImport' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'exceedsSize' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'failedToFetch' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'failedToUpload' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'fileSource' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'filesUploadedOfTotal.0' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'filesUploadedOfTotal.1' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'filesUploadedOfTotal.2' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'filter' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'finishEditingFile' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'folderAdded.0' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'folderAdded.1' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'folderAdded.2' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'import' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'link' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'logOut' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'myDevice' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'noFilesFound' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'noInternetConnection' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'pause' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'pauseUpload' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'paused' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'preparingUpload' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'processingXFiles.0' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'processingXFiles.1' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'processingXFiles.2' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'removeFile' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'resetFilter' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'resume' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'resumeUpload' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'retry' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'retryUpload' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'saveChanges' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'selectXFiles.0' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'selectXFiles.1' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'selectXFiles.2' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'smile' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'startRecording' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'stopRecording' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'takePicture' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'timedOut' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'uploadComplete' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'uploadFailed' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'uploadPaused' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'uploadXFiles.0' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'uploadXFiles.1' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'uploadXFiles.2' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'uploadXNewFiles.0' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'uploadXNewFiles.1' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'uploadXNewFiles.2' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'uploading' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'uploadingXFiles.0' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'uploadingXFiles.1' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'uploadingXFiles.2' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'xFilesSelected.0' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'xFilesSelected.1' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'xFilesSelected.2' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'xMoreFilesAdded.0' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'xMoreFilesAdded.1' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'xMoreFilesAdded.2' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'xTimeLeft' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'youCanOnlyUploadFileTypes' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'youCanOnlyUploadX.0' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'youCanOnlyUploadX.1' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'youCanOnlyUploadX.2' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'youHaveToAtLeastSelectX.0' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'youHaveToAtLeastSelectX.1' that is present in en_US. 
+- [ ] de_DE locale has missing string: 'youHaveToAtLeastSelectX.2' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'addMoreFiles' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'addingMoreFiles' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'allowAccessDescription' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'allowAccessTitle' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'back' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'cancel' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'cancelUpload' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'chooseFiles' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'companionAuthError' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'companionError' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'complete' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'connectedToInternet' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'copyLink' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'creatingAssembly' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'creatingAssemblyFailed' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'dataUploadedOfTotal' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'dropHereOr' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'edit' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'editFile' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'editing' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'emptyFolderAdded' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'encoding' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'enterCorrectUrl' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'enterUrlToImport' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'exceedsSize' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'failedToFetch' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'failedToUpload' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'fileSource' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'filesUploadedOfTotal.0' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'filesUploadedOfTotal.1' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'filesUploadedOfTotal.2' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'filter' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'finishEditingFile' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'folderAdded.0' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'folderAdded.1' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'folderAdded.2' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'import' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'link' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'logOut' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'myDevice' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'noFilesFound' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'noInternetConnection' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'pause' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'pauseUpload' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'paused' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'preparingUpload' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'processingXFiles.0' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'processingXFiles.1' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'processingXFiles.2' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'removeFile' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'resetFilter' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'resume' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'resumeUpload' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'retry' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'retryUpload' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'saveChanges' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'selectXFiles.0' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'selectXFiles.1' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'selectXFiles.2' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'smile' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'startRecording' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'stopRecording' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'takePicture' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'timedOut' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'uploadComplete' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'uploadFailed' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'uploadPaused' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'uploadXFiles.0' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'uploadXFiles.1' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'uploadXFiles.2' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'uploadXNewFiles.0' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'uploadXNewFiles.1' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'uploadXNewFiles.2' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'uploading' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'uploadingXFiles.0' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'uploadingXFiles.1' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'uploadingXFiles.2' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'xFilesSelected.0' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'xFilesSelected.1' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'xFilesSelected.2' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'xMoreFilesAdded.0' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'xMoreFilesAdded.1' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'xMoreFilesAdded.2' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'xTimeLeft' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'youCanOnlyUploadFileTypes' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'youCanOnlyUploadX.0' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'youCanOnlyUploadX.1' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'youCanOnlyUploadX.2' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'youHaveToAtLeastSelectX.0' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'youHaveToAtLeastSelectX.1' that is present in en_US. 
+- [ ] es_ES locale has missing string: 'youHaveToAtLeastSelectX.2' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'addMoreFiles' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'addingMoreFiles' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'allowAccessDescription' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'allowAccessTitle' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'back' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'cancel' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'cancelUpload' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'chooseFiles' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'companionAuthError' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'companionError' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'complete' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'connectedToInternet' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'copyLink' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'creatingAssembly' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'creatingAssemblyFailed' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'dataUploadedOfTotal' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'dropHereOr' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'edit' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'editFile' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'editing' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'emptyFolderAdded' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'encoding' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'enterCorrectUrl' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'enterUrlToImport' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'exceedsSize' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'failedToFetch' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'failedToUpload' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'fileSource' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'filesUploadedOfTotal.0' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'filesUploadedOfTotal.1' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'filesUploadedOfTotal.2' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'filter' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'finishEditingFile' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'folderAdded.0' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'folderAdded.1' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'folderAdded.2' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'import' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'link' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'logOut' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'myDevice' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'noFilesFound' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'noInternetConnection' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'pause' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'pauseUpload' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'paused' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'preparingUpload' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'processingXFiles.0' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'processingXFiles.1' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'processingXFiles.2' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'removeFile' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'resetFilter' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'resume' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'resumeUpload' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'retry' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'retryUpload' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'saveChanges' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'selectXFiles.0' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'selectXFiles.1' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'selectXFiles.2' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'smile' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'startRecording' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'stopRecording' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'takePicture' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'timedOut' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'uploadComplete' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'uploadFailed' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'uploadPaused' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'uploadXFiles.0' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'uploadXFiles.1' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'uploadXFiles.2' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'uploadXNewFiles.0' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'uploadXNewFiles.1' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'uploadXNewFiles.2' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'uploading' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'uploadingXFiles.0' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'uploadingXFiles.1' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'uploadingXFiles.2' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'xFilesSelected.0' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'xFilesSelected.1' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'xFilesSelected.2' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'xMoreFilesAdded.0' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'xMoreFilesAdded.1' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'xMoreFilesAdded.2' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'xTimeLeft' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'youCanOnlyUploadFileTypes' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'youCanOnlyUploadX.0' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'youCanOnlyUploadX.1' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'youCanOnlyUploadX.2' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'youHaveToAtLeastSelectX.0' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'youHaveToAtLeastSelectX.1' that is present in en_US. 
+- [ ] fi_FI locale has missing string: 'youHaveToAtLeastSelectX.2' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'addMoreFiles' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'addingMoreFiles' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'allowAccessDescription' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'allowAccessTitle' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'back' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'cancel' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'cancelUpload' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'chooseFiles' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'companionAuthError' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'companionError' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'complete' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'connectedToInternet' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'copyLink' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'creatingAssembly' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'creatingAssemblyFailed' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'dataUploadedOfTotal' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'dropHereOr' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'edit' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'editFile' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'editing' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'emptyFolderAdded' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'encoding' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'enterCorrectUrl' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'enterUrlToImport' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'exceedsSize' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'failedToFetch' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'failedToUpload' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'fileSource' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'filesUploadedOfTotal.0' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'filesUploadedOfTotal.1' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'filesUploadedOfTotal.2' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'filter' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'finishEditingFile' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'folderAdded.0' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'folderAdded.1' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'folderAdded.2' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'import' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'link' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'logOut' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'myDevice' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'noFilesFound' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'noInternetConnection' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'pause' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'pauseUpload' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'paused' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'preparingUpload' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'processingXFiles.0' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'processingXFiles.1' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'processingXFiles.2' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'removeFile' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'resetFilter' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'resume' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'resumeUpload' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'retry' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'retryUpload' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'saveChanges' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'selectXFiles.0' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'selectXFiles.1' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'selectXFiles.2' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'smile' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'startRecording' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'stopRecording' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'takePicture' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'timedOut' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'uploadComplete' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'uploadFailed' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'uploadPaused' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'uploadXFiles.0' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'uploadXFiles.1' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'uploadXFiles.2' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'uploadXNewFiles.0' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'uploadXNewFiles.1' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'uploadXNewFiles.2' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'uploading' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'uploadingXFiles.0' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'uploadingXFiles.1' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'uploadingXFiles.2' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'xFilesSelected.0' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'xFilesSelected.1' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'xFilesSelected.2' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'xMoreFilesAdded.0' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'xMoreFilesAdded.1' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'xMoreFilesAdded.2' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'xTimeLeft' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'youCanOnlyUploadFileTypes' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'youCanOnlyUploadX.0' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'youCanOnlyUploadX.1' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'youCanOnlyUploadX.2' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'youHaveToAtLeastSelectX.0' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'youHaveToAtLeastSelectX.1' that is present in en_US. 
+- [ ] id_ID locale has missing string: 'youHaveToAtLeastSelectX.2' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'addMoreFiles' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'addingMoreFiles' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'allowAccessDescription' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'allowAccessTitle' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'back' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'cancel' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'cancelUpload' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'chooseFiles' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'companionAuthError' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'companionError' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'complete' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'connectedToInternet' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'copyLink' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'creatingAssembly' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'creatingAssemblyFailed' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'dataUploadedOfTotal' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'dropHereOr' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'edit' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'editFile' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'editing' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'emptyFolderAdded' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'encoding' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'enterCorrectUrl' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'enterUrlToImport' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'exceedsSize' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'failedToFetch' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'failedToUpload' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'fileSource' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'filesUploadedOfTotal.0' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'filesUploadedOfTotal.1' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'filesUploadedOfTotal.2' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'filter' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'finishEditingFile' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'folderAdded.0' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'folderAdded.1' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'folderAdded.2' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'import' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'link' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'logOut' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'myDevice' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'noFilesFound' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'noInternetConnection' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'pause' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'pauseUpload' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'paused' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'preparingUpload' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'processingXFiles.0' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'processingXFiles.1' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'processingXFiles.2' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'removeFile' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'resetFilter' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'resume' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'resumeUpload' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'retry' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'retryUpload' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'saveChanges' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'selectXFiles.0' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'selectXFiles.1' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'selectXFiles.2' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'smile' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'startRecording' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'stopRecording' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'takePicture' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'timedOut' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'uploadComplete' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'uploadFailed' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'uploadPaused' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'uploadXFiles.0' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'uploadXFiles.1' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'uploadXFiles.2' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'uploadXNewFiles.0' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'uploadXNewFiles.1' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'uploadXNewFiles.2' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'uploading' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'uploadingXFiles.0' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'uploadingXFiles.1' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'uploadingXFiles.2' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'xFilesSelected.0' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'xFilesSelected.1' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'xFilesSelected.2' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'xMoreFilesAdded.0' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'xMoreFilesAdded.1' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'xMoreFilesAdded.2' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'xTimeLeft' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'youCanOnlyUploadFileTypes' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'youCanOnlyUploadX.0' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'youCanOnlyUploadX.1' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'youCanOnlyUploadX.2' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'youHaveToAtLeastSelectX.0' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'youHaveToAtLeastSelectX.1' that is present in en_US. 
+- [ ] it_IT locale has missing string: 'youHaveToAtLeastSelectX.2' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'addMoreFiles' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'addingMoreFiles' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'allowAccessDescription' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'allowAccessTitle' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'back' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'cancel' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'cancelUpload' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'chooseFiles' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'companionAuthError' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'companionError' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'complete' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'connectedToInternet' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'copyLink' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'creatingAssembly' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'creatingAssemblyFailed' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'dataUploadedOfTotal' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'dropHereOr' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'edit' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'editFile' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'editing' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'emptyFolderAdded' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'encoding' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'enterCorrectUrl' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'enterUrlToImport' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'exceedsSize' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'failedToFetch' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'failedToUpload' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'fileSource' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'filesUploadedOfTotal.0' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'filesUploadedOfTotal.1' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'filesUploadedOfTotal.2' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'filter' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'finishEditingFile' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'folderAdded.0' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'folderAdded.1' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'folderAdded.2' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'import' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'link' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'logOut' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'myDevice' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'noFilesFound' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'noInternetConnection' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'pause' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'pauseUpload' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'paused' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'preparingUpload' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'processingXFiles.0' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'processingXFiles.1' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'processingXFiles.2' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'removeFile' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'resetFilter' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'resume' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'resumeUpload' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'retry' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'retryUpload' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'saveChanges' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'selectXFiles.0' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'selectXFiles.1' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'selectXFiles.2' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'smile' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'startRecording' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'stopRecording' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'takePicture' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'timedOut' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'uploadComplete' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'uploadFailed' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'uploadPaused' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'uploadXFiles.0' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'uploadXFiles.1' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'uploadXFiles.2' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'uploadXNewFiles.0' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'uploadXNewFiles.1' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'uploadXNewFiles.2' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'uploading' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'uploadingXFiles.0' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'uploadingXFiles.1' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'uploadingXFiles.2' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'xFilesSelected.0' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'xFilesSelected.1' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'xFilesSelected.2' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'xMoreFilesAdded.0' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'xMoreFilesAdded.1' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'xMoreFilesAdded.2' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'xTimeLeft' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'youCanOnlyUploadFileTypes' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'youCanOnlyUploadX.0' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'youCanOnlyUploadX.1' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'youCanOnlyUploadX.2' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'youHaveToAtLeastSelectX.0' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'youHaveToAtLeastSelectX.1' that is present in en_US. 
+- [ ] nb_NO locale has missing string: 'youHaveToAtLeastSelectX.2' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'addMoreFiles' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'addingMoreFiles' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'allowAccessDescription' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'allowAccessTitle' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'back' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'cancel' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'cancelUpload' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'chooseFiles' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'companionAuthError' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'companionError' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'complete' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'connectedToInternet' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'copyLink' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'creatingAssembly' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'creatingAssemblyFailed' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'dataUploadedOfTotal' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'dropHereOr' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'edit' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'editFile' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'editing' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'emptyFolderAdded' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'encoding' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'enterCorrectUrl' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'enterUrlToImport' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'exceedsSize' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'failedToFetch' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'failedToUpload' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'fileSource' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'filesUploadedOfTotal.0' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'filesUploadedOfTotal.1' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'filesUploadedOfTotal.2' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'filter' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'finishEditingFile' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'folderAdded.0' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'folderAdded.1' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'folderAdded.2' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'import' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'link' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'logOut' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'myDevice' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'noFilesFound' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'noInternetConnection' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'pause' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'pauseUpload' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'paused' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'preparingUpload' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'processingXFiles.0' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'processingXFiles.1' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'processingXFiles.2' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'removeFile' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'resetFilter' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'resume' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'resumeUpload' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'retry' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'retryUpload' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'saveChanges' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'selectXFiles.0' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'selectXFiles.1' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'selectXFiles.2' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'smile' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'startRecording' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'stopRecording' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'takePicture' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'timedOut' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'uploadComplete' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'uploadFailed' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'uploadPaused' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'uploadXFiles.0' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'uploadXFiles.1' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'uploadXFiles.2' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'uploadXNewFiles.0' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'uploadXNewFiles.1' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'uploadXNewFiles.2' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'uploading' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'uploadingXFiles.0' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'uploadingXFiles.1' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'uploadingXFiles.2' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'xFilesSelected.0' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'xFilesSelected.1' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'xFilesSelected.2' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'xMoreFilesAdded.0' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'xMoreFilesAdded.1' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'xMoreFilesAdded.2' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'xTimeLeft' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'youCanOnlyUploadFileTypes' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'youCanOnlyUploadX.0' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'youCanOnlyUploadX.1' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'youCanOnlyUploadX.2' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'youHaveToAtLeastSelectX.0' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'youHaveToAtLeastSelectX.1' that is present in en_US. 
+- [ ] pl_PL locale has missing string: 'youHaveToAtLeastSelectX.2' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'addMoreFiles' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'addingMoreFiles' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'allowAccessDescription' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'allowAccessTitle' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'back' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'browse' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'cancel' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'cancelUpload' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'chooseFiles' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'companionAuthError' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'companionError' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'complete' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'connectedToInternet' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'copyLink' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'creatingAssembly' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'creatingAssemblyFailed' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'dataUploadedOfTotal' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'dropHereOr' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'dropPaste' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'edit' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'editFile' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'editing' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'emptyFolderAdded' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'encoding' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'enterCorrectUrl' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'enterUrlToImport' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'exceedsSize' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'failedToFetch' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'failedToUpload' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'fileSource' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'filesUploadedOfTotal.0' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'filesUploadedOfTotal.1' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'filesUploadedOfTotal.2' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'filter' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'finishEditingFile' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'folderAdded.0' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'folderAdded.1' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'folderAdded.2' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'import' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'link' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'logOut' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'myDevice' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'noFilesFound' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'noInternetConnection' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'pause' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'pauseUpload' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'paused' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'preparingUpload' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'processingXFiles.0' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'processingXFiles.1' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'processingXFiles.2' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'removeFile' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'resetFilter' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'resume' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'resumeUpload' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'retry' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'retryUpload' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'saveChanges' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'selectXFiles.0' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'selectXFiles.1' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'selectXFiles.2' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'smile' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'startRecording' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'stopRecording' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'takePicture' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'timedOut' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'uploadComplete' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'uploadFailed' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'uploadPaused' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'uploadXFiles.0' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'uploadXFiles.1' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'uploadXFiles.2' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'uploadXNewFiles.0' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'uploadXNewFiles.1' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'uploadXNewFiles.2' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'uploading' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'uploadingXFiles.0' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'uploadingXFiles.1' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'uploadingXFiles.2' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'xFilesSelected.0' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'xFilesSelected.1' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'xFilesSelected.2' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'xMoreFilesAdded.0' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'xMoreFilesAdded.1' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'xMoreFilesAdded.2' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'xTimeLeft' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'youCanOnlyUploadFileTypes' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'youCanOnlyUploadX.0' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'youCanOnlyUploadX.1' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'youCanOnlyUploadX.2' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'youHaveToAtLeastSelectX.0' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'youHaveToAtLeastSelectX.1' that is present in en_US. 
+- [ ] pt_BR locale has missing string: 'youHaveToAtLeastSelectX.2' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'addMoreFiles' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'addingMoreFiles' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'allowAccessDescription' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'allowAccessTitle' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'back' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'cancel' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'cancelUpload' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'chooseFiles' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'companionAuthError' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'companionError' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'complete' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'connectedToInternet' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'copyLink' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'creatingAssembly' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'creatingAssemblyFailed' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'dataUploadedOfTotal' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'dropHereOr' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'edit' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'editFile' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'editing' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'emptyFolderAdded' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'encoding' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'enterCorrectUrl' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'enterUrlToImport' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'exceedsSize' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'failedToFetch' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'failedToUpload' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'fileSource' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'filesUploadedOfTotal.0' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'filesUploadedOfTotal.1' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'filesUploadedOfTotal.2' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'filter' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'finishEditingFile' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'folderAdded.0' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'folderAdded.1' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'folderAdded.2' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'import' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'link' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'logOut' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'myDevice' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'noFilesFound' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'noInternetConnection' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'pause' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'pauseUpload' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'paused' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'preparingUpload' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'processingXFiles.0' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'processingXFiles.1' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'processingXFiles.2' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'removeFile' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'resetFilter' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'resume' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'resumeUpload' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'retry' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'retryUpload' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'saveChanges' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'selectXFiles.0' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'selectXFiles.1' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'selectXFiles.2' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'smile' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'startRecording' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'stopRecording' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'takePicture' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'timedOut' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'uploadComplete' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'uploadFailed' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'uploadPaused' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'uploadXFiles.0' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'uploadXFiles.1' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'uploadXFiles.2' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'uploadXNewFiles.0' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'uploadXNewFiles.1' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'uploadXNewFiles.2' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'uploading' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'uploadingXFiles.0' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'uploadingXFiles.1' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'uploadingXFiles.2' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'xFilesSelected.0' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'xFilesSelected.1' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'xFilesSelected.2' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'xMoreFilesAdded.0' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'xMoreFilesAdded.1' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'xMoreFilesAdded.2' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'xTimeLeft' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'youCanOnlyUploadFileTypes' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'youCanOnlyUploadX.0' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'youCanOnlyUploadX.1' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'youCanOnlyUploadX.2' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'youHaveToAtLeastSelectX.0' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'youHaveToAtLeastSelectX.1' that is present in en_US. 
+- [ ] tr_TR locale has missing string: 'youHaveToAtLeastSelectX.2' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'addMoreFiles' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'addingMoreFiles' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'allowAccessDescription' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'allowAccessTitle' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'back' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'browse' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'cancel' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'cancelUpload' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'chooseFiles' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'companionAuthError' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'companionError' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'complete' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'connectedToInternet' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'copyLink' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'copyLinkToClipboardFallback' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'copyLinkToClipboardSuccess' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'creatingAssembly' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'creatingAssemblyFailed' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'dashboardTitle' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'dashboardWindowTitle' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'dataUploadedOfTotal' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'done' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'dropHereOr' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'dropPaste' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'dropPasteImport' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'edit' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'editFile' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'editing' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'emptyFolderAdded' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'encoding' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'enterCorrectUrl' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'enterUrlToImport' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'exceedsSize' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'failedToFetch' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'failedToUpload' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'fileSource' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'filesUploadedOfTotal.0' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'filesUploadedOfTotal.1' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'filesUploadedOfTotal.2' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'filter' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'finishEditingFile' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'folderAdded.0' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'folderAdded.1' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'folderAdded.2' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'import' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'importFrom' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'link' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'logOut' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'myDevice' that is present in en_US. 
+- [ ] zh_CN locale has missing string: 'noFilesFound' that is present in en_US. 

+ 63 - 0
packages/@uppy/locales/legacy/cs_CZ.js

@@ -0,0 +1,63 @@
+/* eslint camelcase: 0 */
+
+const cs_CZ = {}
+
+cs_CZ.strings = {
+  chooseFile: 'Vyberte soubor',
+  orDragDrop: 'nebo ho sem přetáhněte',
+  youHaveChosen: 'Vybrali jste: %{fileName}',
+  filesChosen: {
+    0: '%{smart_count} soubor vybrán',
+    1: '%{smart_count} soubory vybrány',
+    2: '%{smart_count} souborů vybráno'
+  },
+  filesUploaded: {
+    0: '%{smart_count} soubor nahrán',
+    1: '%{smart_count} soubory nahrány',
+    2: '%{smart_count} souborů nahráno'
+  },
+  files: {
+    0: '%{smart_count} soubor',
+    1: '%{smart_count} soubory',
+    2: '%{smart_count} souborů'
+  },
+  uploadFiles: {
+    0: 'Nahrát %{smart_count} soubor',
+    1: 'Nahrát %{smart_count} soubory',
+    2: 'Nahrát %{smart_count} souborů'
+  },
+  selectToUpload: 'Vybrat soubory k nahrání',
+  closeModal: 'Zavřít okno',
+  upload: 'Nahrát',
+  importFrom: 'Importovat soubory z',
+  dashboardWindowTitle: 'Uppy Dashboard okno (Pro zavření stiskněte Escape)',
+  dashboardTitle: 'Uppy Dashboard',
+  copyLinkToClipboardSuccess: 'Odkaz zkopírován do schránky.',
+  copyLinkToClipboardFallback: 'Zkopírovat následující odkaz',
+  done: 'Hotovo',
+  localDisk: 'Disk',
+  dropPasteImport: 'Přetáhněte soubory, vložte je, importujte je z některých výše uvedených služeb, nebo',
+  dropPaste: 'Přetáhněte soubory, vložte je, nebo',
+  browse: 'procházejte',
+  fileProgress: 'Nahrávání: rychlost nahrávání a zbývající čas',
+  numberOfSelectedFiles: 'Počet vybraných souborů',
+  uploadAllNewFiles: 'Nahrát všechny nové soubory'
+}
+
+cs_CZ.pluralize = function (n) {
+  if (n === 1) {
+    return 0
+  }
+
+  if (n >= 2 && n <= 4) {
+    return 1
+  }
+
+  return 2
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.cs_CZ = cs_CZ
+}
+
+module.exports = cs_CZ

+ 54 - 0
packages/@uppy/locales/legacy/de_DE.js

@@ -0,0 +1,54 @@
+/* eslint camelcase: 0 */
+
+const de_DE = {}
+
+de_DE.strings = {
+  chooseFile: 'Wähle eine Datei',
+  youHaveChosen: 'Du hast gewählt: %{fileName}',
+  orDragDrop: 'oder schiebe Sie hier her',
+  filesChosen: {
+    0: '%{smart_count} Datei gewählt',
+    1: '%{smart_count} Dateien gewählt'
+  },
+  filesUploaded: {
+    0: '%{smart_count} Datei hochgeladen',
+    1: '%{smart_count} Dateien hochgeladen'
+  },
+  files: {
+    0: '%{smart_count} Datei',
+    1: '%{smart_count} Dateien'
+  },
+  uploadFiles: {
+    0: 'Upload %{smart_count} Datei',
+    1: 'Upload %{smart_count} Dateien'
+  },
+  selectToUpload: 'Ausgewählten Dateien wurden hochgeladen',
+  closeModal: 'Schließen Modal',
+  upload: 'Hochladen',
+  importFrom: 'Importiere Daten von',
+  dashboardWindowTitle: 'Uppy Dashboard Fenster (Drücke Escape zum schließen)',
+  dashboardTitle: 'Uppy Dashboard',
+  copyLinkToClipboardSuccess: 'Link wurde in Zwischenablage kopiert.',
+  copyLinkToClipboardFallback: 'Kopiere die untere URL',
+  done: 'Fertig',
+  localDisk: 'Lokale Festplatte',
+  dropPasteImport: 'Ziehe Dateien hier her, einfügen, importieren aus einer der oberen Quellen oder',
+  dropPaste: 'Zihe Dateien hier her, einfügen oder',
+  browse: 'Durchsuchen',
+  fileProgress: 'Datei Fortschritt: Upload Geschwindigkeit und ETA',
+  numberOfSelectedFiles: 'Anzahl gewählter Dateien',
+  uploadAllNewFiles: 'Alle neuen Dateien hochladen'
+}
+
+de_DE.pluralize = function (n) {
+  if (n === 1) {
+    return 0
+  }
+  return 1
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.de_DE = de_DE
+}
+
+module.exports = de_DE

+ 54 - 0
packages/@uppy/locales/legacy/es_ES.js

@@ -0,0 +1,54 @@
+/* eslint camelcase: 0 */
+
+const es_ES = {}
+
+es_ES.strings = {
+  chooseFile: 'Selecciona un fichero',
+  youHaveChosen: 'Has seleccionado: %{fileName}',
+  orDragDrop: 'o arrástralo aquí',
+  filesChosen: {
+    0: '%{smart_count} fichero seleccionado',
+    1: '%{smart_count} ficheros seleccionados'
+  },
+  filesUploaded: {
+    0: '%{smart_count} fichero subido',
+    1: '%{smart_count} ficheros subidos'
+  },
+  files: {
+    0: '%{smart_count} fichero',
+    1: '%{smart_count} ficheros'
+  },
+  uploadFiles: {
+    0: 'Subir %{smart_count} fichero',
+    1: 'Subir %{smart_count} ficheros'
+  },
+  selectToUpload: 'Selecciona los ficheros a subir',
+  closeModal: 'Cerrar modal',
+  upload: 'Subir',
+  importFrom: 'Importar ficheros desde',
+  dashboardWindowTitle: 'Panel de Uppy (Pulsa escape para cerrar)',
+  dashboardTitle: 'Panel de Uppy',
+  copyLinkToClipboardSuccess: 'Enlace copiado al portapapeles.',
+  copyLinkToClipboardFallback: 'Copiar la siguiente URL',
+  done: 'Hecho',
+  localDisk: 'Disco local',
+  dropPasteImport: 'Arrasta ficheros aquí, pega, importa de alguno de los servicios de arriba o',
+  dropPaste: 'Arrastra ficheros aquí, pega o',
+  browse: 'navegar',
+  fileProgress: 'Progreso: velocidad de subida y tiempo estimado',
+  numberOfSelectedFiles: 'Número de ficheros seleccionados',
+  uploadAllNewFiles: 'Subir todos los nuevos ficheros'
+}
+
+es_ES.pluralize = function (n) {
+  if (n === 1) {
+    return 0
+  }
+  return 1
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.es_ES = es_ES
+}
+
+module.exports = es_ES

+ 54 - 0
packages/@uppy/locales/legacy/fi_FI.js

@@ -0,0 +1,54 @@
+/* eslint camelcase: 0 */
+
+const fi_FI = {}
+
+fi_FI.strings = {
+  chooseFile: 'Valitse tiedosto',
+  youHaveChosen: 'Valitsit: %{fileName}',
+  orDragDrop: 'tai raahaa se tähän',
+  filesChosen: {
+    0: '%{smart_count} tiedosto valittu',
+    1: '%{smart_count} tiedostoa valittu'
+  },
+  filesUploaded: {
+    0: '%{smart_count} tiedosto siirretty',
+    1: '%{smart_count} tiedostoa siirretty'
+  },
+  files: {
+    0: '%{smart_count} tiedosto',
+    1: '%{smart_count} tiedostoa'
+  },
+  uploadFiles: {
+    0: 'Siirrä %{smart_count} tiedosto',
+    1: 'Siirrä %{smart_count} tiedostoa'
+  },
+  selectToUpload: 'Valitse siirrettävät tiedostot',
+  closeModal: 'Sulje ikkuna',
+  upload: 'Siirrä',
+  importFrom: 'Tuo tiedostoja',
+  dashboardWindowTitle: 'Uppy-ohjausnäkymä (Sulje Esc-näppäimellä)',
+  dashboardTitle: 'Uppy-ohjausnäkymä',
+  copyLinkToClipboardSuccess: 'Linkki kopioitu leikepöydälle.',
+  copyLinkToClipboardFallback: 'Kopioi allaoleva linkki',
+  done: 'Valmis',
+  localDisk: 'Paikallinen levy',
+  dropPasteImport: 'Pudota tiedosto(t) tähän, liitä, tuo tiedostoja ylläolevista sijainneista tai',
+  dropPaste: 'Pudota tiedosto(t) tähän, liitä tai',
+  browse: 'selaa',
+  fileProgress: 'Siirron edistyminen: lähetysnopeus ja arvioitu valmistumisaika',
+  numberOfSelectedFiles: 'Valittujen tiedostojen lukumäärä',
+  uploadAllNewFiles: 'Siirrä kaikki uudet tiedostot'
+}
+
+fi_FI.pluralize = function (n) {
+  if (n === 1) {
+    return 0
+  }
+  return 1
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.fi_FI = fi_FI
+}
+
+module.exports = fi_FI

+ 54 - 0
packages/@uppy/locales/legacy/id_ID.js

@@ -0,0 +1,54 @@
+/* eslint camelcase: 0 */
+
+const id_ID = {}
+
+id_ID.strings = {
+  chooseFile: 'Pilih berkas',
+  youHaveChosen: 'Berkas yang dipilih: %{fileName}',
+  orDragDrop: 'atau tarik dan taruh berkas ke sini',
+  filesChosen: {
+    0: '%{smart_count} berkas dipilih',
+    1: '%{smart_count} berkas dipilih'
+  },
+  filesUploaded: {
+    0: '%{smart_count} berkas terunggah',
+    1: '%{smart_count} berkas terunggah'
+  },
+  files: {
+    0: '%{smart_count} berkas',
+    1: '%{smart_count} berkas'
+  },
+  uploadFiles: {
+    0: 'Unggah %{smart_count} berkas',
+    1: 'Unggah %{smart_count} berkas'
+  },
+  selectToUpload: 'Pilih berkas untuk mengunggah',
+  closeModal: 'Tutup Modal',
+  upload: 'Unggah',
+  importFrom: 'Import berkas dari',
+  dashboardWindowTitle: 'Uppy Beranda Window (Tekan escape untuk menutup)',
+  dashboardTitle: 'Beranda Uppy',
+  copyLinkToClipboardSuccess: 'Link tersalin.',
+  copyLinkToClipboardFallback: 'Salin URL di bawah ini',
+  done: 'Selesai',
+  localDisk: 'Penyimpanan Lokal',
+  dropPasteImport: 'Taruh berkas di sini, tempel, import dari salah satu lokasi di atas atau',
+  dropPaste: 'Taruh berkas di sini, tempel atau',
+  browse: 'cari',
+  fileProgress: 'Proses berkas: kecepatan unggah dan ETA',
+  numberOfSelectedFiles: 'Total berkas yang di pilih',
+  uploadAllNewFiles: 'Unggah semua berkas baru'
+}
+
+id_ID.pluralize = function (n) {
+  if (n === 1) {
+    return 0
+  }
+  return 1
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.id_ID = id_ID
+}
+
+module.exports = id_ID

+ 54 - 0
packages/@uppy/locales/legacy/it_IT.js

@@ -0,0 +1,54 @@
+/* eslint camelcase: 0 */
+
+const it_IT = {}
+
+it_IT.strings = {
+  chooseFile: 'Seleziona un file',
+  youHaveChosen: 'Hai scelto: %{fileName}',
+  orDragDrop: 'oppure trascinalo qui',
+  filesChosen: {
+    0: '%{smart_count} file selezionato',
+    1: '%{smart_count} file selezionati'
+  },
+  filesUploaded: {
+    0: '%{smart_count} file caricato',
+    1: '%{smart_count} file caricati'
+  },
+  files: {
+    0: '%{smart_count} file',
+    1: '%{smart_count} file'
+  },
+  uploadFiles: {
+    0: 'Carica %{smart_count} file',
+    1: 'Carica %{smart_count} file'
+  },
+  selectToUpload: 'Seleziona i file da caricare',
+  closeModal: 'Chiudi la finestra',
+  upload: 'Carica',
+  importFrom: 'Importa i file da',
+  dashboardWindowTitle: 'Uppy Dashboard Window (Premi escape per chiuderla)',
+  dashboardTitle: 'Uppy Dashboard',
+  copyLinkToClipboardSuccess: 'Collegamento copiato negli appunti.',
+  copyLinkToClipboardFallback: 'Copia il seguente indirizzo',
+  done: 'Fatto',
+  localDisk: 'Disco locale',
+  dropPasteImport: 'Trascina i file qui, incolla, importa da uno dei servizi sopra oppure',
+  dropPaste: 'Trascina i file qui, incolla oppure',
+  browse: 'sfoglia',
+  fileProgress: 'Avanzamento del file: velocità di caricamento e tempo rimanente',
+  numberOfSelectedFiles: 'Numero di file selezionati',
+  uploadAllNewFiles: 'Carica tutti i nuovi file'
+}
+
+it_IT.pluralize = function (n) {
+  if (n === 1) {
+    return 0
+  }
+  return 1
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.it_IT = it_IT
+}
+
+module.exports = it_IT

+ 54 - 0
packages/@uppy/locales/legacy/nb_NO.js

@@ -0,0 +1,54 @@
+/* eslint camelcase: 0 */
+
+const nb_NO = {}
+
+nb_NO.strings = {
+  chooseFile: 'Velg en fil',
+  youHaveChosen: 'Du har valgt: %{fileName}',
+  orDragDrop: 'eller slipp den her',
+  filesChosen: {
+    0: '%{smart_count} fil valgt',
+    1: '%{smart_count} filer valgt'
+  },
+  filesUploaded: {
+    0: '%{smart_count} fil lastet opp',
+    1: '%{smart_count} filer lastet opp'
+  },
+  files: {
+    0: '%{smart_count} fil',
+    1: '%{smart_count} filer'
+  },
+  uploadFiles: {
+    0: 'Lastet opp %{smart_count} fil',
+    1: 'Lastet opp %{smart_count} filer'
+  },
+  selectToUpload: 'Velg filer å laste opp',
+  closeModal: 'Lukk dialogboksen',
+  upload: 'Last opp',
+  importFrom: 'Importer filer fra',
+  dashboardWindowTitle: 'Uppy Dashboard-vindu (Trykk escape for å lukke)',
+  dashboardTitle: 'Uppy Dashboard',
+  copyLinkToClipboardSuccess: 'Lenken ble kopiert til utklippstavla.',
+  copyLinkToClipboardFallback: 'Kopier URL-en under',
+  done: 'Ferdig',
+  localDisk: 'Lokal disk',
+  dropPasteImport: 'Du kan slippe eller lime inn filer her, importere fra en en av plasseringene ovenfor eller',
+  dropPaste: 'Du kan slippe eller lime inn filer her eller',
+  browse: 'velge dem',
+  fileProgress: 'Filstatus: Opplastingshastighet og ETA',
+  numberOfSelectedFiles: 'Antall valgte filer',
+  uploadAllNewFiles: 'Last opp alle nye filer'
+}
+
+nb_NO.pluralize = function (n) {
+  if (n === 1) {
+    return 0
+  }
+  return 1
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.nb_NO = nb_NO
+}
+
+module.exports = nb_NO

+ 61 - 0
packages/@uppy/locales/legacy/pl_PL.js

@@ -0,0 +1,61 @@
+/* eslint camelcase: 0 */
+
+const pl_PL = {}
+
+pl_PL.strings = {
+  chooseFile: 'Wybierz plik',
+  youHaveChosen: 'Wybrałeś: %{fileName}',
+  orDragDrop: 'lub przeciągnij tutaj',
+  filesChosen: {
+    0: '%{smart_count} wybrany plik',
+    1: '%{smart_count} wybrane pliki',
+    2: '%{smart_count} wybranych plików'
+  },
+  filesUploaded: {
+    0: '%{smart_count} wysłany plik',
+    1: '%{smart_count} wysłane pliki',
+    2: '%{smart_count} wysłanych plików'
+  },
+  files: {
+    0: '%{smart_count} plik',
+    1: '%{smart_count} pliki',
+    2: '%{smart_count} plików'
+  },
+  uploadFiles: {
+    0: 'Wyślij %{smart_count} plik',
+    1: 'Wyślij %{smart_count} pliki',
+    2: 'Wyślij %{smart_count} plików'
+  },
+  selectToUpload: 'Wybierz pliki do wysłania',
+  closeModal: 'Zamknij okno',
+  upload: 'Wyślij',
+  importFrom: 'Zaimportuj pliki z',
+  dashboardWindowTitle: 'Okno Uppy Dashboard (Wciśnij esc, aby zamknąć)',
+  dashboardTitle: 'Uppy Dashboard',
+  copyLinkToClipboardSuccess: 'Link skopiowany do schowka.',
+  copyLinkToClipboardFallback: 'Skopiuj poniższy link',
+  done: 'Gotowe',
+  localDisk: 'Dysk lokalny',
+  dropPasteImport: 'Upuść, wklej lub zaimportuj pliki tutaj albo',
+  dropPaste: 'Upuść lub wklej pliki tutaj albo',
+  browse: 'przeglądaj',
+  fileProgress: 'Postęp pliku: prędkość wysyłania i przewidywany pozostały czas',
+  numberOfSelectedFiles: 'Ilość wybranych plików',
+  uploadAllNewFiles: 'Wyślij wszystkie nowe pliki'
+}
+
+pl_PL.pluralize = function (n) {
+  if (n === 1) {
+    return 0
+  }
+  if (n >= 2 && n <= 4) {
+    return 1
+  }
+  return 2
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.pl_PL = pl_PL
+}
+
+module.exports = pl_PL

+ 52 - 0
packages/@uppy/locales/legacy/pt_BR.js

@@ -0,0 +1,52 @@
+/* eslint camelcase: 0 */
+
+const pt_BR = {}
+
+pt_BR.strings = {
+  chooseFile: 'Escolha um arquivo',
+  youHaveChosen: 'Você escolheu: %{fileName}',
+  orDragDrop: 'ou arraste-o aqui',
+  filesChosen: {
+    0: '%{smart_count} arquivo selecionado',
+    1: '%{smart_count} arquivos selecionados'
+  },
+  filesUploaded: {
+    0: '%{smart_count} arquivo enviado',
+    1: '%{smart_count} arquivos enviados'
+  },
+  files: {
+    0: '%{smart_count} arquivo',
+    1: '%{smart_count} arquivos'
+  },
+  uploadFiles: {
+    0: 'Enviar %{smart_count} arquivo',
+    1: 'Enviar %{smart_count} arquivos'
+  },
+  selectToUpload: 'Selecione arquivos para enviar',
+  closeModal: 'Fechar Modal',
+  upload: 'Enviar',
+  importFrom: 'Importar arquivos de',
+  dashboardWindowTitle: 'Painel do Uppy (Aperte Esc para fechar)',
+  dashboardTitle: 'Painel do Uppy',
+  copyLinkToClipboardSuccess: 'Link copiado para área de transferência.',
+  copyLinkToClipboardFallback: 'Copie a URL abaixo',
+  done: 'Finalizado',
+  localDisk: 'Disco Local',
+  dropPasteImport: 'Arraste arquivos até aqui, cole-os ou importe de:',
+  fileProgress: 'Progresso de Arquivo: velocidade de envio e estimativas',
+  numberOfSelectedFiles: 'Números de arquivos selecionados',
+  uploadAllNewFiles: 'Enviar todos os arquivos novos'
+}
+
+pt_BR.pluralize = function (n) {
+  if (n === 1) {
+    return 0
+  }
+  return 1
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.pt_BR = pt_BR
+}
+
+module.exports = pt_BR

+ 54 - 0
packages/@uppy/locales/legacy/tr_TR.js

@@ -0,0 +1,54 @@
+/* eslint camelcase: 0 */
+
+const tr_TR = {}
+
+tr_TR.strings = {
+  chooseFile: 'Dosya Seçin',
+  youHaveChosen: 'Seçmiş olduğun dosya: %{fileName}',
+  orDragDrop: 'yada bırakın',
+  filesChosen: {
+    0: '%{smart_count} adet dosya seçili',
+    1: '%{smart_count} adet dosyalar seçili'
+  },
+  filesUploaded: {
+    0: '%{smart_count} adet dosya yüklendi',
+    1: '%{smart_count} adet dosyalar yüklendi'
+  },
+  files: {
+    0: '%{smart_count} dosya',
+    1: '%{smart_count} dosyalar'
+  },
+  uploadFiles: {
+    0: 'Yüklenen %{smart_count} dosya',
+    1: 'Yüklenen %{smart_count} dosyalar'
+  },
+  selectToUpload: 'Yüklemek için dosyaları seçin',
+  closeModal: 'Pencereyi Kapat',
+  upload: 'Yükle',
+  importFrom: 'Dosyaları içeri aktar',
+  dashboardWindowTitle: 'Uppy Panel Pencerisi (kapatmak için esc kullanın)',
+  dashboardTitle: 'Uppy Panel',
+  copyLinkToClipboardSuccess: 'Bağlantı kopyalandı.',
+  copyLinkToClipboardFallback: 'Bağlantıyı kopyala.',
+  done: 'Bitti',
+  localDisk: 'Lokal Dosyalar',
+  dropPasteImport: 'Dosyaları buraya bırakın, yukarıdaki konumlardan birinden yapıştırın, içeri aktarın veya',
+  dropPaste: 'Dosyaları buraya bırak, yapıştır veya',
+  browse: 'Gözat',
+  fileProgress: 'Dosya ilerlemesi: yükleme hızı ve süresi',
+  numberOfSelectedFiles: 'Seçilen dosya sayısı',
+  uploadAllNewFiles: 'Tüm yeni dosyaları yükle'
+}
+
+tr_TR.pluralize = function (n) {
+  if (n === 1) {
+    return 0
+  }
+  return 1
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.tr_TR = tr_TR
+}
+
+module.exports = tr_TR

+ 34 - 0
packages/@uppy/locales/legacy/zh_CN.js

@@ -0,0 +1,34 @@
+/* eslint camelcase: 0 */
+
+const zh_CN = {}
+
+zh_CN.strings = {
+  chooseFile: '选择文件',
+  youHaveChosen: '你已经选择了: %{fileName}',
+  orDragDrop: '或者拖到这里来',
+  filesChosen: {
+    0: '已选 %{smart_count} 个文件'
+  },
+  filesUploaded: {
+    0: '已上传 %{smart_count} 个文件'
+  },
+  files: {
+    0: '%{smart_count} 个文件'
+  },
+  uploadFiles: {
+    0: '上传 %{smart_count} 个文件'
+  },
+  selectToUpload: '选择文件以上传',
+  closeModal: '关闭对话框',
+  upload: '上传'
+}
+
+zh_CN.pluralize = function (n) {
+  return 0
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.zh_CN = zh_CN
+}
+
+module.exports = zh_CN

+ 19 - 0
packages/@uppy/locales/package.json

@@ -0,0 +1,19 @@
+{
+  "name": "@uppy/locales",
+  "description": "",
+  "version": "0.30.4",
+  "license": "MIT",
+  "keywords": [
+    "uppy",
+    "uppy-plugin",
+    "language packs"
+  ],
+  "homepage": "https://uppy.io",
+  "bugs": {
+    "url": "https://github.com/transloadit/uppy/issues"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/transloadit/uppy.git"
+  }
+}

+ 143 - 0
packages/@uppy/locales/src/en_US.js

@@ -0,0 +1,143 @@
+/* eslint camelcase: 0 */
+
+const en_US = {}
+
+en_US.strings = {
+  addMoreFiles: 'Add more files',
+  addingMoreFiles: 'Adding more files',
+  allowAccessDescription: 'In order to take pictures or record video with your camera, please allow camera access for this site.',
+  allowAccessTitle: 'Please allow access to your camera',
+  back: 'Back',
+  browse: 'browse',
+  cancel: 'Cancel',
+  cancelUpload: 'Cancel upload',
+  chooseFiles: 'Choose files',
+  closeModal: 'Close Modal',
+  companionAuthError: 'Authorization required',
+  companionError: 'Connection with Companion failed',
+  complete: 'Complete',
+  connectedToInternet: 'Connected to the Internet',
+  copyLink: 'Copy link',
+  copyLinkToClipboardFallback: 'Copy the URL below',
+  copyLinkToClipboardSuccess: 'Link copied to clipboard',
+  creatingAssembly: 'Preparing upload...',
+  creatingAssemblyFailed: 'Transloadit: Could not create Assembly',
+  dashboardTitle: 'Uppy Dashboard',
+  dashboardWindowTitle: 'Uppy Dashboard Window (Press escape to close)',
+  dataUploadedOfTotal: '%{complete} of %{total}',
+  done: 'Done',
+  dropHereOr: 'Drop files here or %{browse}',
+  dropPaste: 'Drop files here, paste or %{browse}',
+  dropPasteImport: 'Drop files here, paste, %{browse} or import from',
+  edit: 'Edit',
+  editFile: 'Edit file',
+  editing: 'Editing %{file}',
+  emptyFolderAdded: 'No files were added from empty folder',
+  encoding: 'Encoding...',
+  enterCorrectUrl: 'Incorrect URL: Please make sure you are entering a direct link to a file',
+  enterUrlToImport: 'Enter URL to import a file',
+  exceedsSize: 'This file exceeds maximum allowed size of',
+  failedToFetch: 'Companion failed to fetch this URL, please make sure it’s correct',
+  failedToUpload: 'Failed to upload %{file}',
+  fileSource: 'File source: %{name}',
+  filesUploadedOfTotal: {
+    '0': '%{complete} of %{smart_count} file uploaded',
+    '1': '%{complete} of %{smart_count} files uploaded',
+    '2': '%{complete} of %{smart_count} files uploaded'
+  },
+  filter: 'Filter',
+  finishEditingFile: 'Finish editing file',
+  folderAdded: {
+    '0': 'Added %{smart_count} file from %{folder}',
+    '1': 'Added %{smart_count} files from %{folder}',
+    '2': 'Added %{smart_count} files from %{folder}'
+  },
+  import: 'Import',
+  importFrom: 'Import from %{name}',
+  link: 'Link',
+  logOut: 'Log out',
+  myDevice: 'My Device',
+  noFilesFound: 'You have no files or folders here',
+  noInternetConnection: 'No Internet connection',
+  pause: 'Pause',
+  pauseUpload: 'Pause upload',
+  paused: 'Paused',
+  preparingUpload: 'Preparing upload...',
+  processingXFiles: {
+    '0': 'Processing %{smart_count} file',
+    '1': 'Processing %{smart_count} files',
+    '2': 'Processing %{smart_count} files'
+  },
+  removeFile: 'Remove file',
+  resetFilter: 'Reset filter',
+  resume: 'Resume',
+  resumeUpload: 'Resume upload',
+  retry: 'Retry',
+  retryUpload: 'Retry upload',
+  saveChanges: 'Save changes',
+  selectXFiles: {
+    '0': 'Select %{smart_count} file',
+    '1': 'Select %{smart_count} files',
+    '2': 'Select %{smart_count} files'
+  },
+  smile: 'Smile!',
+  startRecording: 'Begin video recording',
+  stopRecording: 'Stop video recording',
+  takePicture: 'Take a picture',
+  timedOut: 'Upload stalled for %{seconds} seconds, aborting.',
+  upload: 'Upload',
+  uploadComplete: 'Upload complete',
+  uploadFailed: 'Upload failed',
+  uploadPaused: 'Upload paused',
+  uploadXFiles: {
+    '0': 'Upload %{smart_count} file',
+    '1': 'Upload %{smart_count} files',
+    '2': 'Upload %{smart_count} files'
+  },
+  uploadXNewFiles: {
+    '0': 'Upload +%{smart_count} file',
+    '1': 'Upload +%{smart_count} files',
+    '2': 'Upload +%{smart_count} files'
+  },
+  uploading: 'Uploading',
+  uploadingXFiles: {
+    '0': 'Uploading %{smart_count} file',
+    '1': 'Uploading %{smart_count} files',
+    '2': 'Uploading %{smart_count} files'
+  },
+  xFilesSelected: {
+    '0': '%{smart_count} file selected',
+    '1': '%{smart_count} files selected',
+    '2': '%{smart_count} files selected'
+  },
+  xMoreFilesAdded: {
+    '0': '%{smart_count} more file added',
+    '1': '%{smart_count} more files added',
+    '2': '%{smart_count} more files added'
+  },
+  xTimeLeft: '%{time} left',
+  youCanOnlyUploadFileTypes: 'You can only upload: %{types}',
+  youCanOnlyUploadX: {
+    '0': 'You can only upload %{smart_count} file',
+    '1': 'You can only upload %{smart_count} files',
+    '2': 'You can only upload %{smart_count} files'
+  },
+  youHaveToAtLeastSelectX: {
+    '0': 'You have to select at least %{smart_count} file',
+    '1': 'You have to select at least %{smart_count} files',
+    '2': 'You have to select at least %{smart_count} files'
+  }
+}
+
+en_US.pluralize = function (n) {
+  if (n === 1) {
+    return 0
+  }
+  return 1
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.en_US = en_US
+}
+
+module.exports = en_US

+ 148 - 0
packages/@uppy/locales/src/ru_RU.js

@@ -0,0 +1,148 @@
+/* eslint camelcase: 0 */
+
+const ru_RU = {}
+
+ru_RU.strings = {
+  addMoreFiles: 'Добавить дополнительные файлы',
+  addingMoreFiles: 'Добавление дополнительных файлов',
+  allowAccessDescription: 'Чтобы сделать фото или видео с помощью вашей камеры, пожалуйста разрешите доступ к камере для этого сайта',
+  allowAccessTitle: 'Пожалуйста, разрешите доступ к камере',
+  back: 'Назад',
+  browse: 'выберите',
+  cancel: 'Отмена',
+  cancelUpload: 'Отменить загрузку',
+  chooseFiles: 'Выбрать файлы',
+  closeModal: 'Закрыть окно',
+  companionAuthError: 'Требуется авторизация',
+  companionError: 'Connection with Companion failed',
+  complete: 'Готово',
+  connectedToInternet: 'Подключено к интернету',
+  copyLink: 'Скопировать ссылку',
+  copyLinkToClipboardFallback: 'Copy the URL below',
+  copyLinkToClipboardSuccess: 'Ссылка скопирована в буфер обмена',
+  creatingAssembly: 'Preparing upload...',
+  creatingAssemblyFailed: 'Transloadit: Could not create Assembly',
+  dashboardTitle: 'Uppy Dashboard',
+  dashboardWindowTitle: 'Uppy Dashboard Window (Press escape to close)',
+  dataUploadedOfTotal: '%{complete} of %{total}',
+  done: 'Готово',
+  dropHereOr: 'Перетащите файлы сюда или %{browse}',
+  dropPaste: 'Drop files here, paste or %{browse}',
+  dropPasteImport: 'Перенесите файлы сюда, вставьте, %{browse} или импортируйте',
+  edit: 'Редактировать',
+  editFile: 'Редактировать файл',
+  editing: 'Редактируется %{file}',
+  emptyFolderAdded: 'No files were added from empty folder',
+  encoding: 'Encoding...',
+  enterCorrectUrl: 'Incorrect URL: Please make sure you are entering a direct link to a file',
+  enterUrlToImport: 'Введите адрес URL, чтобы импортировать файл',
+  exceedsSize: 'This file exceeds maximum allowed size of',
+  failedToFetch: 'Companion failed to fetch this URL, please make sure it’s correct',
+  failedToUpload: 'Failed to upload %{file}',
+  fileSource: 'File source: %{name}',
+  filesUploadedOfTotal: {
+    '0': '%{complete} of %{smart_count} file uploaded',
+    '1': '%{complete} of %{smart_count} files uploaded',
+    '2': '%{complete} of %{smart_count} files uploaded'
+  },
+  filter: 'Фильтр',
+  finishEditingFile: 'Finish editing file',
+  folderAdded: {
+    '0': 'Added %{smart_count} file from %{folder}',
+    '1': 'Added %{smart_count} files from %{folder}',
+    '2': 'Added %{smart_count} files from %{folder}'
+  },
+  import: 'Импортировать',
+  importFrom: 'Импортировать из %{name}',
+  link: 'Ссылка',
+  logOut: 'Выйти',
+  myDevice: 'Мое устройство',
+  noFilesFound: 'You have no files or folders here',
+  noInternetConnection: 'Нет подключения к интернету',
+  pause: 'Поставить на паузу',
+  pauseUpload: 'Pause upload',
+  paused: 'На паузе',
+  preparingUpload: 'Приготовление к загрузке...',
+  processingXFiles: {
+    '0': 'Processing %{smart_count} file',
+    '1': 'Processing %{smart_count} files',
+    '2': 'Processing %{smart_count} files'
+  },
+  removeFile: 'Remove file',
+  resetFilter: 'Reset filter',
+  resume: 'Resume',
+  resumeUpload: 'Resume upload',
+  retry: 'Retry',
+  retryUpload: 'Retry upload',
+  saveChanges: 'Сохранить изменения',
+  selectXFiles: {
+    '0': 'Select %{smart_count} file',
+    '1': 'Select %{smart_count} files',
+    '2': 'Select %{smart_count} files'
+  },
+  smile: 'Smile!',
+  startRecording: 'Begin video recording',
+  stopRecording: 'Stop video recording',
+  takePicture: 'Take a picture',
+  timedOut: 'Upload stalled for %{seconds} seconds, aborting.',
+  upload: 'Upload',
+  uploadComplete: 'Upload complete',
+  uploadFailed: 'Upload failed',
+  uploadPaused: 'Upload paused',
+  uploadXFiles: {
+    '0': 'Upload %{smart_count} file',
+    '1': 'Upload %{smart_count} files',
+    '2': 'Upload %{smart_count} files'
+  },
+  uploadXNewFiles: {
+    '0': 'Upload +%{smart_count} file',
+    '1': 'Upload +%{smart_count} files',
+    '2': 'Upload +%{smart_count} files'
+  },
+  uploading: 'Uploading',
+  uploadingXFiles: {
+    '0': 'Uploading %{smart_count} file',
+    '1': 'Uploading %{smart_count} files',
+    '2': 'Uploading %{smart_count} files'
+  },
+  xFilesSelected: {
+    '0': '%{smart_count} file selected',
+    '1': '%{smart_count} files selected',
+    '2': '%{smart_count} files selected'
+  },
+  xMoreFilesAdded: {
+    '0': '%{smart_count} more file added',
+    '1': '%{smart_count} more files added',
+    '2': '%{smart_count} more files added'
+  },
+  xTimeLeft: '%{time} left',
+  youCanOnlyUploadFileTypes: 'You can only upload: %{types}',
+  youCanOnlyUploadX: {
+    '0': 'You can only upload %{smart_count} file',
+    '1': 'You can only upload %{smart_count} files',
+    '2': 'You can only upload %{smart_count} files'
+  },
+  youHaveToAtLeastSelectX: {
+    '0': 'You have to select at least %{smart_count} file',
+    '1': 'You have to select at least %{smart_count} files',
+    '2': 'You have to select at least %{smart_count} files'
+  }
+}
+
+ru_RU.pluralize = function (n) {
+  if (n % 10 === 1 && n % 100 !== 11) {
+    return 0
+  }
+
+  if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) {
+    return 1
+  }
+
+  return 2
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.ru_RU = ru_RU
+}
+
+module.exports = ru_RU

+ 18 - 0
packages/@uppy/locales/template.js

@@ -0,0 +1,18 @@
+/* eslint camelcase: 0 */
+
+const en_US = {}
+
+en_US.strings = {}
+
+en_US.pluralize = function (n) {
+  if (n === 1) {
+    return 0
+  }
+  return 1
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.en_US = en_US
+}
+
+module.exports = en_US

+ 3 - 3
packages/@uppy/progress-bar/src/style.scss

@@ -19,8 +19,8 @@
 
 .uppy-ProgressBar-inner {
   /* no important */
-  background-color: $color-cornflower-blue;
-  box-shadow: 0 0 10px rgba($color-cornflower-blue, 0.7);
+  background-color: $blue;
+  box-shadow: 0 0 10px rgba($blue, 0.7);
   height: 100%;
   width: 0;
   transition: width 0.4s ease;
@@ -34,5 +34,5 @@
   top: 50%;
   left: 50%;
   transform: translate(-50%, -50%);
-  color: $color-white;
+  color: $white;
 }

+ 3 - 3
packages/@uppy/provider-views/src/Filter.js

@@ -17,9 +17,6 @@ module.exports = class Filter extends Component {
 
   render () {
     return <div class="uppy-ProviderBrowser-search">
-      <svg aria-hidden="true" class="UppyIcon uppy-ProviderBrowser-searchIcon" viewBox="0 0 100 100">
-        <path d="M87.533 80.03L62.942 55.439c3.324-4.587 5.312-10.207 5.312-16.295 0-.312-.043-.611-.092-.908.05-.301.093-.605.093-.922 0-15.36-12.497-27.857-27.857-27.857-.273 0-.536.043-.799.08-.265-.037-.526-.08-.799-.08-15.361 0-27.858 12.497-27.858 27.857 0 .312.042.611.092.909a5.466 5.466 0 0 0-.093.921c0 15.36 12.496 27.858 27.857 27.858.273 0 .535-.043.8-.081.263.038.524.081.798.081 5.208 0 10.071-1.464 14.245-3.963L79.582 87.98a5.603 5.603 0 0 0 3.976 1.647 5.621 5.621 0 0 0 3.975-9.597zM39.598 55.838c-.265-.038-.526-.081-.8-.081-9.16 0-16.612-7.452-16.612-16.612 0-.312-.042-.611-.092-.908.051-.301.093-.605.093-.922 0-9.16 7.453-16.612 16.613-16.612.272 0 .534-.042.799-.079.263.037.525.079.799.079 9.16 0 16.612 7.452 16.612 16.612 0 .312.043.611.092.909-.05.301-.094.604-.094.921 0 9.16-7.452 16.612-16.612 16.612-.274 0-.536.043-.798.081z" />
-      </svg>
       <input
         class="uppy-u-reset uppy-ProviderBrowser-searchInput"
         type="text"
@@ -30,6 +27,9 @@ module.exports = class Filter extends Component {
         onkeypress={this.handleKeyPress}
         value={this.props.filterInput}
         ref={(input) => { this.input = input }} />
+      <svg aria-hidden="true" class="UppyIcon uppy-ProviderBrowser-searchIcon" width="12" height="12" viewBox="0 0 12 12">
+        <path d="M8.638 7.99l3.172 3.172a.492.492 0 1 1-.697.697L7.91 8.656a4.977 4.977 0 0 1-2.983.983C2.206 9.639 0 7.481 0 4.819 0 2.158 2.206 0 4.927 0c2.721 0 4.927 2.158 4.927 4.82a4.74 4.74 0 0 1-1.216 3.17zm-3.71.685c2.176 0 3.94-1.726 3.94-3.856 0-2.129-1.764-3.855-3.94-3.855C2.75.964.984 2.69.984 4.819c0 2.13 1.765 3.856 3.942 3.856z" />
+      </svg>
       { this.props.filterInput &&
         <button
           class="uppy-u-reset uppy-ProviderBrowser-searchClose"

+ 4 - 4
packages/@uppy/provider-views/src/index.js

@@ -153,7 +153,7 @@ module.exports = class ProviderView {
         fileId: file.id
       },
       remote: {
-        serverUrl: this.plugin.opts.serverUrl,
+        companionUrl: this.plugin.opts.companionUrl,
         url: `${this.provider.fileUrl(file.requestPath)}`,
         body: {
           fileId: file.id
@@ -218,7 +218,7 @@ module.exports = class ProviderView {
 
   filterItems (items) {
     const state = this.plugin.getPluginState()
-    if (state.filterInput === '') {
+    if (!state.filterInput || state.filterInput === '') {
       return items
     }
     return items.filter((folder) => {
@@ -423,8 +423,8 @@ module.exports = class ProviderView {
 
     const authWindow = window.open(link, '_blank')
     const handleToken = (e) => {
-      if (!this._isOriginAllowed(e.origin, this.plugin.opts.serverPattern) || e.source !== authWindow) {
-        this.plugin.uppy.log(`rejecting event from ${e.origin} vs allowed pattern ${this.plugin.opts.serverPattern}`)
+      if (!this._isOriginAllowed(e.origin, this.plugin.opts.companionAllowedHosts) || e.source !== authWindow) {
+        this.plugin.uppy.log(`rejecting event from ${e.origin} vs allowed pattern ${this.plugin.opts.companionAllowedHosts}`)
         return
       }
       authWindow.close()

+ 92 - 68
packages/@uppy/provider-views/src/style.scss

@@ -17,6 +17,11 @@
   justify-content: center;
   flex-flow: column wrap;
   flex: 1;
+  color: $gray-500;
+}
+
+.uppy-Provider-empty {
+  color: $gray-500;
 }
 
 .uppy-Provider-authIcon svg {
@@ -33,6 +38,7 @@
   padding: 0 15px;
   max-width: 500px;
   text-align: center;
+  color: $gray-600;
 
   .uppy-size--md & {
     font-size: 20px;
@@ -40,11 +46,9 @@
 }
 
 .uppy-Provider-breadcrumbs {
-  display: inline-block;
   flex: 1;
-  color: darken($color-gray, 25%);
+  color: $gray-700;
   font-size: 12px;
-  line-height: 1;
   margin-bottom: 10px;
   text-align: left;
 
@@ -54,9 +58,9 @@
 }
 
 .uppy-Provider-breadcrumbsIcon {
-  display: inline;
-  color: darken($color-gray, 25%);
-  vertical-align: text-bottom;
+  display: inline-block;
+  color: $gray-700;
+  vertical-align: bottom;
   margin-right: 8px;
   line-height: 1;
 }
@@ -64,12 +68,13 @@
   .uppy-Provider-breadcrumbsIcon svg {
     width: 13px;
     height: 13px;
-    fill: darken($color-gray, 25%);
+    fill: $gray-700;
   }
 
 .uppy-Provider-breadcrumbs button {
   display: inline-block;
-  font-size: 14px;
+  line-height: inherit;
+  // font-size: 14px;
 }
 
 .uppy-Provider-breadcrumbs button:hover {
@@ -81,34 +86,38 @@
   display: flex;
   flex-direction: column;
   flex: 1;
-  font-size: 13px;
+  font-size: 14px;
   font-weight: 400;
   height: 100%;
 }
 
 .uppy-ProviderBrowser-user {
   margin: 0 8px 0 0;
-  line-height: 1;
+  font-weight: 500;
+  color: $gray-800;
 }
 
   .uppy-ProviderBrowser-user:after {
     content: '\00B7';
     position: relative;
     left: 4px;
+    color: $gray-500;
+    font-weight: normal;
   }
 
 .uppy-ProviderBrowser-header {
   z-index: $zIndex-2;
-  border-bottom: 1px solid rgba($color-gray, 0.3);
+  border-bottom: 1px solid $gray-200;
   position: relative;
 }
 
 .uppy-ProviderBrowser-headerBar {
   padding: 12px 15px;
-  background-color: lighten($color-gray, 40%);
+  background-color: $gray-50;
   z-index: $zIndex-2;
-  color: darken($color-gray, 20%);
-  line-height: 1;
+  color: $gray-600;
+  line-height: 1.4;
+  font-size: 12px;
 
   .uppy-size--md & {
     display: flex;
@@ -133,22 +142,22 @@
 
 .uppy-ProviderBrowser-search {
   width: 100%;
-  background-color: $color-white;
+  background-color: $white;
   position: relative;
   height: 30px;
-  margin-top: 5px;
+  margin-top: 10px;
   margin-bottom: 5px;
   display: flex;
   align-items: center;
 }
 
 .uppy-ProviderBrowser-searchIcon {
-  width: 16px;
-  height: 16px;
+  position: absolute;
+  width: 12px;
+  height: 12px;
+  left: 16px;
   z-index: $zIndex-3;
-  color: rgba($color-gray, 0.6);
-  margin-left: 16px;
-  margin-right: 7px;
+  color: $gray-400;
 }
 
 .uppy-ProviderBrowser-searchInput {
@@ -157,44 +166,49 @@
   background-color: transparent;
   outline: 0;
   font-family: $font-family-base;
-  font-size: 13px;
-  line-height: 30px;
+  font-size: 12px;
+  line-height: 1.4;
   border: 0;
-  margin: 0 16px 0 0;
+  margin: 0 8px;
+  padding-left: 27px;
   z-index: $zIndex-2;
-  border-bottom: 1px solid transparent;
+  border-radius: 4px;
 }
 
   .uppy-ProviderBrowser-searchInput:focus {
     outline: 0;
-    border-bottom: 1px solid $color-cornflower-blue;
+    background-color: $gray-100;
   }
 
 .uppy-ProviderBrowser-searchClose {
-  width: 12px;
-  height: 12px;
   position: absolute;
-  right: 16px;
-  top: 8px;
+  width: 22px;
+  height: 22px;
+  padding: 6px;
+  right: 12px;
+  top: 4px;
   z-index: $zIndex-3;
-  color: $color-gray;
+  color: $gray-500;
   cursor: pointer;
+
+  &:hover {
+    color: $gray-600;
+  }
 }
 
   .uppy-ProviderBrowser-searchClose svg {
     vertical-align: text-top;
   }
 
-.uppy-ProviderBrowser-searchInput::placeholder,
-.uppy-ProviderBrowser-searchInput::-webkit-input-placeholder,
-.uppy-ProviderBrowser-searchInput::-moz-placeholder,
-.uppy-ProviderBrowser-searchInput::-ms-input-placeholder {
-  color: rgba($color-gray, 0.85);
-  letter-spacing: 1px;
+.uppy-ProviderBrowser-searchInput::placeholder {
+  color: $gray-500;
+  opacity: 1;
 }
 
 .uppy-ProviderBrowser-userLogout {
   cursor: pointer;
+  line-height: inherit;
+  color: $blue;
 
   &:hover {
     text-decoration: underline;
@@ -212,7 +226,7 @@
   display: block;
   width: 100%;
   height: 100%;
-  background-color: $color-white;
+  background-color: $white;
   border-spacing: 0;
   overflow-x: hidden;
   overflow-y: auto;
@@ -229,7 +243,8 @@
 
 .uppy-ProviderBrowserItem-inner {
   cursor: pointer;
-  font-weight: 600;
+  font-weight: 500;
+  font-size: 13px;
 }
 
 // ***
@@ -237,11 +252,11 @@
 // ***
 
 .uppy-ProviderBrowser-viewType--list {
-  background-color: $color-white;
+  background-color: $white;
 
   .uppy-ProviderBrowserItem {
     display: flex;
-    padding: 10px 15px;
+    padding: 7px 15px;
     margin: 0;
   }
 
@@ -250,11 +265,11 @@
   }
 
     .uppy-ProviderBrowserItem-checkbox label:before {
-      border-color: rgba($color-asphalt-gray, 0.4);
+      border-color: $gray-300;
     }
 
     .uppy-ProviderBrowserItem-checkbox input:checked + label:before {
-      border-color: $color-cornflower-blue;
+      border-color: $blue;
     }
 
   .uppy-ProviderBrowserItem-inner {
@@ -308,12 +323,12 @@
     }
 
     // .uppy-ProviderBrowserItem--selected {
-    //   border-color: $color-cornflower-blue;
+    //   border-color: $blue;
     //   outline: none;
     // }
 
     // .uppy-ProviderBrowserItem--selected .uppy-ProviderBrowserItem-inner {
-    //   box-shadow: 0 0 0 3px rgba(darken($color-cornflower-blue, 10%), 0.9);
+    //   box-shadow: 0 0 0 3px rgba(darken($blue, 10%), 0.9);
     // }
 
   .uppy-ProviderBrowserItem-inner {
@@ -325,52 +340,61 @@
     right: 7px;
     bottom: 7px;
     text-align: center;
+
   }
 
   .uppy-ProviderBrowserItem-inner:focus {
     outline: none;
-    box-shadow: 0 0 0 3px rgba($color-cornflower-blue, 0.9);
+    box-shadow: 0 0 0 3px rgba($blue, 0.9);
   }
 
   .uppy-ProviderBrowserItem img,
   .uppy-ProviderBrowserItem svg {
     width: 100%;
     height: 100%;
-    vertical-align: middle;
     object-fit: cover;
+    border-radius: 4px;
+  }
+
+  .uppy-ProviderBrowserItem--selected img,
+  .uppy-ProviderBrowserItem--selected svg {
+    opacity: 0.85;
   }
 
   .uppy-ProviderBrowserItem--noPreview .uppy-ProviderBrowserItem-inner {
-    background-color: rgba($color-gray, 0.3);
+    background-color: rgba($gray-500, 0.3);
   }
 
   .uppy-ProviderBrowserItem--noPreview svg {
-    fill: rgba($color-black, 0.7);
+    fill: rgba($black, 0.7);
     width: 30%;
     height: 30%;
   }
 
   .uppy-ProviderBrowserItem-checkbox {
     position: absolute;
-    top: 13px;
-    right: 22px;
+    width: 26px;
+    height: 26px;
+    top: 16px;
+    right: 16px;
+    display: block;
     margin-right: 0;
-    opacity: 0.95;
     z-index: $zIndex-3;
   }
 
   .uppy-ProviderBrowserItem-checkbox label:before {
-    background-color: $color-cornflower-blue;
+    background-color: $blue;
     border-radius: 50%;
     width: 26px;
     height: 26px;
+    top: 0;
   }
 
   .uppy-ProviderBrowserItem-checkbox label:after {
     width: 12px;
     height: 7px;
     left: 7px;
-    top: 10px;
+    top: 8px;
   }
 
   // Hide checkbox when unchecked in grid view
@@ -418,12 +442,12 @@
 .uppy-ProviderBrowserItem-checkbox label:before {
   content: "";
   display: inline-block;
-  height: 18px;
-  width: 18px;
+  height: 17px;
+  width: 17px;
   top: 2px;
-  border: 1px solid $color-cornflower-blue;
-  background-color: $color-white;
-  border-radius: 2px;
+  border: 1px solid $blue;
+  background-color: $white;
+  border-radius: 3px;
 }
 
 // Inner checkbox
@@ -431,11 +455,11 @@
   content: '';
   display: inline-block;
   height: 5px;
-  width: 8px;
-  left: 5px;
-  top: 8px;
-  border-left: 2px solid $color-white;
-  border-bottom: 2px solid $color-white;
+  width: 9px;
+  left: 4px;
+  top: 7px;
+  border-left: 2px solid $white;
+  border-bottom: 2px solid $white;
   transform: rotate(-45deg);
 }
 
@@ -450,7 +474,7 @@
 }
 
 .uppy-ProviderBrowserItem-checkbox input:checked + label::before {
-  background-color: $color-cornflower-blue;
+  background-color: $blue;
 }
 
 // Adding focus styles on the outer-box of the fake checkbox*/
@@ -461,12 +485,12 @@
 .uppy-ProviderBrowser-footer {
   display: flex;
   align-items: center;
-  background: $color-white;
+  background: $white;
   height: 65px;
-  border-top: 1px solid rgba($color-gray, 0.3);
+  border-top: 1px solid $gray-200;
   padding: 0 15px;
 
   & button {
-    margin-right: 10px;
+    margin-right: 8px;
   }
 }

+ 4 - 4
packages/@uppy/robodog/src/addProviders.js

@@ -12,8 +12,8 @@ const localProviders = {
 }
 
 const remoteProviderOptionNames = [
-  'serverUrl',
-  'serverPattern',
+  'companionUrl',
+  'companionAllowedHosts',
   'serverHeaders',
   'target'
 ]
@@ -27,8 +27,8 @@ function addRemoteProvider (uppy, name, opts) {
   const Provider = remoteProviders[name]
   const providerOptions = {
     // Default to the :tl: Companion servers.
-    serverUrl: Transloadit.COMPANION,
-    serverPattern: Transloadit.COMPANION_PATTERN
+    companionUrl: Transloadit.COMPANION,
+    companionAllowedHosts: Transloadit.COMPANION_PATTERN
   }
 
   remoteProviderOptionNames.forEach((name) => {

+ 42 - 17
packages/@uppy/status-bar/src/StatusBar.js

@@ -158,10 +158,15 @@ const UploadBtn = (props) => {
 }
 
 const RetryBtn = (props) => {
-  return <button type="button"
-    class="uppy-u-reset uppy-c-btn uppy-StatusBar-actionBtn uppy-StatusBar-actionBtn--retry"
-    aria-label={props.i18n('retryUpload')}
-    onclick={props.retryAll}>{props.i18n('retry')}</button>
+  return (
+    <button type="button"
+      class="uppy-u-reset uppy-c-btn uppy-StatusBar-actionBtn uppy-StatusBar-actionBtn--retry" aria-label={props.i18n('retryUpload')} onclick={props.retryAll}>
+      <svg aria-hidden="true" class="UppyIcon" width="8" height="10" viewBox="0 0 8 10">
+        <path d="M4 2.408a2.75 2.75 0 1 0 2.75 2.75.626.626 0 0 1 1.25.018v.023a4 4 0 1 1-4-4.041V.25a.25.25 0 0 1 .389-.208l2.299 1.533a.25.25 0 0 1 0 .416l-2.3 1.533A.25.25 0 0 1 4 3.316v-.908z" />
+      </svg>
+      {props.i18n('retry')}
+    </button>
+  )
 }
 
 const CancelBtn = (props) => {
@@ -171,8 +176,11 @@ const CancelBtn = (props) => {
     title={props.i18n('cancel')}
     aria-label={props.i18n('cancel')}
     onclick={props.cancelAll}>
-    <svg aria-hidden="true" class="UppyIcon" width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
-      <path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm1.414-8l2.122-2.121-1.415-1.415L8 6.586 5.879 4.464 4.464 5.88 6.586 8l-2.122 2.121 1.415 1.415L8 9.414l2.121 2.122 1.415-1.415L9.414 8z" fill="#949494" fill-rule="evenodd" />
+    <svg aria-hidden="true" class="UppyIcon" width="16" height="16" viewBox="0 0 16 16">
+      <g fill="none" fill-rule="evenodd">
+        <circle fill="#888" cx="8" cy="8" r="8" />
+        <path fill="#FFF" d="M9.283 8l2.567 2.567-1.283 1.283L8 9.283 5.433 11.85 4.15 10.567 6.717 8 4.15 5.433 5.433 4.15 8 6.717l2.567-2.567 1.283 1.283z" />
+      </g>
     </svg>
   </button>
 }
@@ -188,18 +196,24 @@ const PauseResumeButton = (props) => {
     type="button"
     onclick={() => togglePauseResume(props)}>
     {isAllPaused
-      ? <svg aria-hidden="true" class="UppyIcon" width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
-        <path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zM6 5v6l5-3-5-3z" fill="#949494" fill-rule="evenodd" />
+      ? <svg aria-hidden="true" class="UppyIcon" width="16" height="16" viewBox="0 0 16 16">
+        <g fill="none" fill-rule="evenodd">
+          <circle fill="#888" cx="8" cy="8" r="8" />
+          <path fill="#FFF" d="M6 4.25L11.5 8 6 11.75z" />
+        </g>
       </svg>
-      : <svg aria-hidden="true" class="UppyIcon" width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
-        <path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zM5 5v6h2V5H5zm4 0v6h2V5H9z" fill="#949494" fill-rule="evenodd" />
+      : <svg aria-hidden="true" class="UppyIcon" width="16" height="16" viewBox="0 0 16 16">
+        <g fill="none" fill-rule="evenodd">
+          <circle fill="#888" cx="8" cy="8" r="8" />
+          <path d="M5 4.5h2v7H5v-7zm4 0h2v7H9v-7z" fill="#FFF" />
+        </g>
       </svg>
     }
   </button>
 }
 
 const LoadingSpinner = (props) => {
-  return <svg class="uppy-StatusBar-spinner" width="14" height="14" xmlns="http://www.w3.org/2000/svg">
+  return <svg class="uppy-StatusBar-spinner" width="14" height="14">
     <path d="M13.983 6.547c-.12-2.509-1.64-4.893-3.939-5.936-2.48-1.127-5.488-.656-7.556 1.094C.524 3.367-.398 6.048.162 8.562c.556 2.495 2.46 4.52 4.94 5.183 2.932.784 5.61-.602 7.256-3.015-1.493 1.993-3.745 3.309-6.298 2.868-2.514-.434-4.578-2.349-5.153-4.84a6.226 6.226 0 0 1 2.98-6.778C6.34.586 9.74 1.1 11.373 3.493c.407.596.693 1.282.842 1.988.127.598.073 1.197.161 1.794.078.525.543 1.257 1.15.864.525-.341.49-1.05.456-1.592-.007-.15.02.3 0 0" fill-rule="evenodd" />
   </svg>
 }
@@ -281,18 +295,29 @@ const ProgressBarUploading = (props) => {
 const ProgressBarComplete = ({ totalProgress, i18n }) => {
   return (
     <div class="uppy-StatusBar-content" role="status" title={i18n('complete')}>
-      <svg aria-hidden="true" class="uppy-StatusBar-statusIndicator UppyIcon" width="18" height="17" viewBox="0 0 23 17">
-        <path d="M8.944 17L0 7.865l2.555-2.61 6.39 6.525L20.41 0 23 2.645z" />
-      </svg>
-      {i18n('complete')}
+      <div class="uppy-StatusBar-status">
+        <div class="uppy-StatusBar-statusPrimary">
+          <svg aria-hidden="true" class="uppy-StatusBar-statusIndicator UppyIcon" width="15" height="11" viewBox="0 0 15 11">
+            <path d="M.414 5.843L1.627 4.63l3.472 3.472L13.202 0l1.212 1.213L5.1 10.528z" />
+          </svg>
+          {i18n('complete')}
+        </div>
+      </div>
     </div>
   )
 }
 
 const ProgressBarError = ({ error, retryAll, hideRetryButton, i18n }) => {
   return (
-    <div class="uppy-StatusBar-content" role="alert">
-      <span class="uppy-StatusBar-contentPadding">{i18n('uploadFailed')}.</span>
+    <div class="uppy-StatusBar-content" role="alert" title={i18n('uploadFailed')}>
+      <div class="uppy-StatusBar-status">
+        <div class="uppy-StatusBar-statusPrimary">
+          <svg aria-hidden="true" class="uppy-StatusBar-statusIndicator UppyIcon" width="11" height="11" viewBox="0 0 11 11">
+            <path d="M4.278 5.5L0 1.222 1.222 0 5.5 4.278 9.778 0 11 1.222 6.722 5.5 11 9.778 9.778 11 5.5 6.722 1.222 11 0 9.778z" />
+          </svg>
+          {i18n('uploadFailed')}
+        </div>
+      </div>
       {/* {!hideRetryButton &&
         <span class="uppy-StatusBar-contentPadding">{i18n('pleasePressRetry')}</span>
       } */}

+ 10 - 14
packages/@uppy/status-bar/src/index.js

@@ -16,41 +16,38 @@ module.exports = class StatusBar extends Plugin {
     this.title = 'StatusBar'
     this.type = 'progressindicator'
 
-    const defaultLocale = {
+    this.defaultLocale = {
       strings: {
         uploading: 'Uploading',
         upload: 'Upload',
         complete: 'Complete',
         uploadFailed: 'Upload failed',
-        pleasePressRetry: 'Please press Retry to upload again',
         paused: 'Paused',
-        error: 'Error',
         retry: 'Retry',
         cancel: 'Cancel',
         pause: 'Pause',
         resume: 'Resume',
-        pressToRetry: 'Press to retry',
-        // retryUpload: 'Retry upload',
-        // resumeUpload: 'Resume upload',
-        // cancelUpload: 'Cancel upload',
-        // pauseUpload: 'Pause upload',
         filesUploadedOfTotal: {
           0: '%{complete} of %{smart_count} file uploaded',
-          1: '%{complete} of %{smart_count} files uploaded'
+          1: '%{complete} of %{smart_count} files uploaded',
+          2: '%{complete} of %{smart_count} files uploaded'
         },
         dataUploadedOfTotal: '%{complete} of %{total}',
         xTimeLeft: '%{time} left',
         uploadXFiles: {
           0: 'Upload %{smart_count} file',
-          1: 'Upload %{smart_count} files'
+          1: 'Upload %{smart_count} files',
+          2: 'Upload %{smart_count} files'
         },
         uploadXNewFiles: {
           0: 'Upload +%{smart_count} file',
-          1: 'Upload +%{smart_count} files'
+          1: 'Upload +%{smart_count} files',
+          2: 'Upload +%{smart_count} files'
         },
         xMoreFilesAdded: {
           0: '%{smart_count} more file added',
-          1: '%{smart_count} more files added'
+          1: '%{smart_count} more files added',
+          2: '%{smart_count} more files added'
         }
       }
     }
@@ -63,14 +60,13 @@ module.exports = class StatusBar extends Plugin {
       hidePauseResumeButton: false,
       hideCancelButton: false,
       showProgressDetails: false,
-      locale: defaultLocale,
       hideAfterFinish: true
     }
 
     // merge default options with the ones set by user
     this.opts = Object.assign({}, defaultOptions, opts)
 
-    this.translator = new Translator([ defaultLocale, this.uppy.locale, this.opts.locale ])
+    this.translator = new Translator([ this.defaultLocale, this.uppy.locale, this.opts.locale ])
     this.i18n = this.translator.translate.bind(this.translator)
 
     this.startUpload = this.startUpload.bind(this)

+ 70 - 71
packages/@uppy/status-bar/src/style.scss

@@ -9,15 +9,14 @@
   line-height: 40px;
   font-size: 12px;
   font-weight: 400;
-  color: $color-white;
-  background-color: $color-white;
+  color: $white;
+  background-color: $white;
   z-index: $zIndex-2;
   transition: height .2s;
 }
 
   .uppy-size--md .uppy-StatusBar {
     height: 46px;
-    font-size: 14px;
   }
 
   .uppy-StatusBar:before {
@@ -29,7 +28,7 @@
     bottom: 0;
     width: 100%;
     height: 2px;
-    background-color: rgba($color-gray, 0.25);
+    background-color: $gray-200;
   }
 
 .uppy-StatusBar[aria-hidden=true] {
@@ -38,26 +37,29 @@
 }
 
 .uppy-StatusBar.is-complete .uppy-StatusBar-progress {
-  background-color: $color-green;
+  background-color: $green;
 }
 
 .uppy-StatusBar.is-error .uppy-StatusBar-progress {
-  background-color: $color-red;
+  background-color: $red;
 }
 
 .uppy-StatusBar.is-complete .uppy-StatusBar-statusIndicator {
-  cursor: default;
-  color: $color-green;
+  color: $green;
+}
+
+.uppy-StatusBar.is-error .uppy-StatusBar-statusIndicator {
+  color: $red;
 }
 
 .uppy-StatusBar:not([aria-hidden=true]).is-waiting {
-  background-color: $color-white;
+  background-color: $white;
   height: 65px;
-  border-top: 1px solid rgba($color-gray, 0.3);
+  border-top: 1px solid $gray-200;
 }
 
 .uppy-StatusBar-progress {
-  background-color: $color-cornflower-blue;
+  background-color: $blue;
   height: 2px;
   position: absolute;
   z-index: $zIndex-2;
@@ -78,7 +80,7 @@
 
   .uppy-StatusBar.is-preprocessing .uppy-StatusBar-progress,
   .uppy-StatusBar.is-postprocessing .uppy-StatusBar-progress {
-    background-color: $color-orange;
+    background-color: $orange;
   }
 
 .uppy-StatusBar.is-waiting .uppy-StatusBar-progress {
@@ -93,7 +95,7 @@
   padding-left: 10px;
   white-space: nowrap;
   text-overflow: ellipsis;
-  color: $color-black;
+  color: $gray-800;
   height: 100%;
 
   .uppy-size--md & {
@@ -101,10 +103,6 @@
   }
 }
 
-.uppy-StatusBar-contentPadding {
-  margin-right: 0.5ch; // ½ the size of a 0, roughly the size of a space usually
-}
-
 .uppy-StatusBar-status {
   line-height: 1.4;
   font-weight: normal;
@@ -113,36 +111,27 @@
   justify-content: center;
   max-width: 170px;
   overflow-x: hidden;
+  padding-right: 0.3em;
 
   .uppy-size--md & {
-    line-height: 1.5;
     max-width: 400px;
   }
 }
 
 .uppy-StatusBar-statusPrimary {
-  font-size: 12px;
-  font-weight: 400;
-
-  .uppy-size--md & {
-    font-size: 13px;
-  }
+  font-weight: 500;
 }
 
 .uppy-StatusBar-statusSecondary {
-  font-size: 10px;
-  // line-height: 1;
+  margin-top: 1px;
+  font-size: 11px;
+  line-height: 1.2;
   display: inline-block;
-  color: rgba($color-asphalt-gray, 0.8);
+  color: $gray-600;
   text-overflow: ellipsis;
   white-space: nowrap;
   overflow-x: hidden;
   // max-width: 170px;
-
-  .uppy-size--md & {
-    font-size: 11px;
-    // max-width: 500px;
-  }
 }
 
   .uppy-StatusBar-statusSecondaryHint {
@@ -161,23 +150,15 @@
   // }
 
 .uppy-StatusBar-statusIndicator {
-  color: $color-asphalt-gray;
-  margin-right: 10px;
-  cursor: pointer;
-
-  .uppy-size--md & {
-    margin-right: 12px;
-  }
-}
+  position: relative;
+  top: 1px;
+  color: $gray-700;
+  margin-right: 7px;
 
-  .uppy-StatusBar-statusIndicator svg {
+  svg {
     vertical-align: text-bottom;
   }
-
-  .uppy-StatusBar.is-complete .uppy-StatusBar-statusIndicator  {
-    width: 15px;
-    margin-right: 7px;
-  }
+}
 
 .uppy-StatusBar-actions {
   display: flex;
@@ -193,28 +174,28 @@
   width: 100%;
   position: static;
   padding: 0 15px;
-  background-color: $color-almost-white;
+  background-color: $gray-50;
 }
 
 .uppy-StatusBar-actionCircleBtn {
   line-height: 1;
   cursor: pointer;
-  padding: 4px;
+  padding: 3px;
+  opacity: 0.9;
   // display: flex;
   // align-items: center;
   // justify-content: center;
   // width: 20px;
   // height: 20px;
   // border-radius: 50%;
-  // color: rgba($color-black, 0.6);
-  // background-color: rgba($color-gray, 0.3);
+  // color: rgba($black, 0.6);
+  // background-color: rgba($gray-500, 0.3);
   // text-align: center;
-}
 
-  .uppy-StatusBar-actionCircleBtn:focus {
-    outline: 2px solid $color-cornflower-blue;
-    outline-offset: 0;
+  &:hover {
+    opacity: 1;
   }
+}
 
   // .uppy-StatusBar-actionCircleBtn:not(:last-child) {
   //   margin-right: 2px;
@@ -235,9 +216,8 @@
   display: inline-block;
   vertical-align: middle;
   font-size: 10px;
-  // margin-left: 6px;
-  // margin-right: 6px;
-  color: $color-cornflower-blue;
+  line-height: inherit;
+  color: $blue;
 
   .uppy-size--md & {
     font-size: 11px;
@@ -248,31 +228,49 @@
   //   padding: 2px 4px;
   // }
 
+  .uppy-StatusBar-actionBtn--retry {
+    height: 16px;
+    border-radius: 8px;
+    margin-right: 6px;
+    background-color: $pomegranate;
+    line-height: 1;
+    color: #fff;
+    padding: 1px 6px 3px 18px;
+    position: relative;
+
+    &:hover {
+      background-color: darken($pomegranate, 8%);
+    }
+
+    svg {
+      position: absolute;
+      top: 3px;
+      left: 6px;
+    }
+  }
+
   .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn--upload {
     font-size: 14px;
     width: 100%;
     padding: 15px 10px;
-    color: $color-white;
-    background-color: $color-green;
+    color: $white;
+    background-color: $green;
+    line-height: 1;
   }
 
     .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn--upload:hover {
-      background-color: darken($color-green, 10%);
+      background-color: darken($green, 10%);
     }
 
     .uppy-size--md .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn--upload {
-      padding: 16px 22px;
+      padding: 13px 22px;
       width: auto;
     }
 
-  .uppy-StatusBar-actionBtn:not(:last-child) {
-    margin-right: 10px;
-  }
-
   .uppy-StatusBar:not(.is-waiting) .uppy-StatusBar-actionBtn--upload {
     background-color: transparent;
-    // border: 1px solid $color-white;
-    color: $color-cornflower-blue;
+    // border: 1px solid $white;
+    color: $blue;
   }
 
 .uppy-StatusBar-details {
@@ -281,13 +279,14 @@
   height: 13px;
   display: inline-block;
   vertical-align: middle;
-  color: $color-white;
-  background-color: $color-asphalt-gray;
+  color: $white;
+  background-color: $gray-500;
   border-radius: 50%;
   position: relative;
   top: 0;
   left: 2px;
   font-size: 10px;
+  font-weight: 600;
   text-align: center;
   cursor: help;
 }
@@ -303,12 +302,12 @@
   animation-iteration-count: infinite;
   animation-timing-function: linear;
   margin-right: 10px;
-  fill: $color-cornflower-blue;
+  fill: $blue;
 }
 
   .uppy-StatusBar.is-preprocessing .uppy-StatusBar-spinner,
   .uppy-StatusBar.is-postprocessing .uppy-StatusBar-spinner {
-    fill: $color-orange;
+    fill: $orange;
   }
 
 

+ 53 - 22
packages/@uppy/transloadit/src/AssemblyOptions.test.js

@@ -1,7 +1,7 @@
 const AssemblyOptions = require('./AssemblyOptions')
 
 describe('Transloadit/AssemblyOptions', () => {
-  it('Validates response from getAssemblyOptions()', () => {
+  it('Validates response from getAssemblyOptions()', async () => {
     const options = new AssemblyOptions([
       { name: 'testfile' }
     ], {
@@ -13,12 +13,12 @@ describe('Transloadit/AssemblyOptions', () => {
       }
     })
 
-    return expect(options.build()).rejects.toThrow(
+    await expect(options.build()).rejects.toThrow(
       /The `params\.auth\.key` option is required/
     )
   })
 
-  it('Uses different assemblies for different params', () => {
+  it('Uses different assemblies for different params', async () => {
     const data = Buffer.alloc(10)
     data.size = data.byteLength
 
@@ -38,16 +38,15 @@ describe('Transloadit/AssemblyOptions', () => {
       })
     })
 
-    return options.build().then((assemblies) => {
-      expect(assemblies).toHaveLength(4)
-      expect(assemblies[0].options.params.steps.fake_step.data).toBe('a.png')
-      expect(assemblies[1].options.params.steps.fake_step.data).toBe('b.png')
-      expect(assemblies[2].options.params.steps.fake_step.data).toBe('c.png')
-      expect(assemblies[3].options.params.steps.fake_step.data).toBe('d.png')
-    })
+    const assemblies = await options.build()
+    expect(assemblies).toHaveLength(4)
+    expect(assemblies[0].options.params.steps.fake_step.data).toBe('a.png')
+    expect(assemblies[1].options.params.steps.fake_step.data).toBe('b.png')
+    expect(assemblies[2].options.params.steps.fake_step.data).toBe('c.png')
+    expect(assemblies[3].options.params.steps.fake_step.data).toBe('d.png')
   })
 
-  it('Should merge files with same parameters into one Assembly', () => {
+  it('Should merge files with same parameters into one Assembly', async () => {
     const data = Buffer.alloc(10)
     const data2 = Buffer.alloc(20)
 
@@ -67,26 +66,25 @@ describe('Transloadit/AssemblyOptions', () => {
       })
     })
 
-    return options.build().then((assemblies) => {
-      expect(assemblies).toHaveLength(2)
-      expect(assemblies[0].fileIDs).toHaveLength(3)
-      expect(assemblies[1].fileIDs).toHaveLength(1)
-      expect(assemblies[0].options.params.steps.fake_step.data).toBe(10)
-      expect(assemblies[1].options.params.steps.fake_step.data).toBe(20)
-    })
+    const assemblies = await options.build()
+    expect(assemblies).toHaveLength(2)
+    expect(assemblies[0].fileIDs).toHaveLength(3)
+    expect(assemblies[1].fileIDs).toHaveLength(1)
+    expect(assemblies[0].options.params.steps.fake_step.data).toBe(10)
+    expect(assemblies[1].options.params.steps.fake_step.data).toBe(20)
   })
 
-  it('Does not create an Assembly if no files are being uploaded', () => {
+  it('Does not create an Assembly if no files are being uploaded', async () => {
     const options = new AssemblyOptions([], {
       getAssemblyOptions () {
         throw new Error('should not create Assembly')
       }
     })
 
-    return expect(options.build()).resolves.toEqual([])
+    await expect(options.build()).resolves.toEqual([])
   })
 
-  it('Creates an Assembly if no files are being uploaded but `alwaysRunAssembly` is enabled', () => {
+  it('Creates an Assembly if no files are being uploaded but `alwaysRunAssembly` is enabled', async () => {
     const options = new AssemblyOptions([], {
       alwaysRunAssembly: true,
       getAssemblyOptions (file) {
@@ -100,6 +98,39 @@ describe('Transloadit/AssemblyOptions', () => {
       }
     })
 
-    return expect(options.build()).resolves.toHaveLength(1)
+    await expect(options.build()).resolves.toHaveLength(1)
+  })
+
+  it('Collects metadata if `fields` is an array', async () => {
+    function defaultGetAssemblyOptions (file, options) {
+      return {
+        params: options.params,
+        signature: options.signature,
+        fields: options.fields
+      }
+    }
+
+    const options = new AssemblyOptions([{
+      id: 1,
+      meta: { watermark: 'Some text' }
+    }, {
+      id: 2,
+      meta: { watermark: 'ⓒ Transloadit GmbH' }
+    }], {
+      fields: ['watermark'],
+      params: {
+        auth: { key: 'fake key' }
+      },
+      getAssemblyOptions: defaultGetAssemblyOptions
+    })
+
+    const assemblies = await options.build()
+    expect(assemblies).toHaveLength(2)
+    expect(assemblies[0].options.fields).toMatchObject({
+      watermark: 'Some text'
+    })
+    expect(assemblies[1].options.fields).toMatchObject({
+      watermark: 'ⓒ Transloadit GmbH'
+    })
   })
 })

+ 11 - 0
packages/@uppy/transloadit/src/AssemblyWatcher.js

@@ -22,6 +22,7 @@ class TransloaditAssemblyWatcher extends Emitter {
     })
 
     this._onAssemblyComplete = this._onAssemblyComplete.bind(this)
+    this._onAssemblyCancel = this._onAssemblyCancel.bind(this)
     this._onAssemblyError = this._onAssemblyError.bind(this)
     this._onImportError = this._onImportError.bind(this)
 
@@ -47,6 +48,14 @@ class TransloaditAssemblyWatcher extends Emitter {
     this._checkAllComplete()
   }
 
+  _onAssemblyCancel (assembly) {
+    if (!this._watching(assembly.assembly_id)) {
+      return
+    }
+
+    this._checkAllComplete()
+  }
+
   _onAssemblyError (assembly, error) {
     if (!this._watching(assembly.assembly_id)) {
       return
@@ -84,12 +93,14 @@ class TransloaditAssemblyWatcher extends Emitter {
 
   _removeListeners () {
     this._uppy.off('transloadit:complete', this._onAssemblyComplete)
+    this._uppy.off('transloadit:assembly-cancel', this._onAssemblyCancel)
     this._uppy.off('transloadit:assembly-error', this._onAssemblyError)
     this._uppy.off('transloadit:import-error', this._onImportError)
   }
 
   _addListeners () {
     this._uppy.on('transloadit:complete', this._onAssemblyComplete)
+    this._uppy.on('transloadit:assembly-cancel', this._onAssemblyCancel)
     this._uppy.on('transloadit:assembly-error', this._onAssemblyError)
     this._uppy.on('transloadit:import-error', this._onImportError)
   }

+ 22 - 0
packages/@uppy/transloadit/src/Client.js

@@ -46,12 +46,24 @@ module.exports = class Client {
     })
   }
 
+  /**
+   * Reserve resources for a file in an Assembly. Then addFile can be used later.
+   *
+   * @param {object} assembly
+   * @param {UppyFile} file
+   */
   reserveFile (assembly, file) {
     const size = encodeURIComponent(file.size)
     return fetch(`${assembly.assembly_ssl_url}/reserve_file?size=${size}`, { method: 'post' })
       .then((response) => response.json())
   }
 
+  /**
+   * Import a remote file to an Assembly.
+   *
+   * @param {object} assembly
+   * @param {UppyFile} file
+   */
   addFile (assembly, file) {
     if (!file.uploadURL) {
       return Promise.reject(new Error('File does not have an `uploadURL`.'))
@@ -66,6 +78,16 @@ module.exports = class Client {
       .then((response) => response.json())
   }
 
+  /**
+   * Cancel a running Assembly.
+   *
+   * @param {object} assembly
+   */
+  cancelAssembly (assembly) {
+    return fetch(assembly.assembly_ssl_url, { method: 'delete' })
+      .then((response) => response.json())
+  }
+
   /**
    * Get the current status for an assembly.
    *

+ 59 - 16
packages/@uppy/transloadit/src/index.js

@@ -31,7 +31,7 @@ module.exports = class Transloadit extends Plugin {
     this.id = 'Transloadit'
     this.title = 'Transloadit'
 
-    const defaultLocale = {
+    this.defaultLocale = {
       strings: {
         creatingAssembly: 'Preparing upload...',
         creatingAssemblyFailed: 'Transloadit: Could not create Assembly',
@@ -48,8 +48,7 @@ module.exports = class Transloadit extends Plugin {
       signature: null,
       params: null,
       fields: {},
-      getAssemblyOptions: defaultGetAssemblyOptions,
-      locale: defaultLocale
+      getAssemblyOptions: defaultGetAssemblyOptions
     }
 
     this.opts = {
@@ -58,13 +57,14 @@ module.exports = class Transloadit extends Plugin {
     }
 
     // i18n
-    this.translator = new Translator([ defaultLocale, this.uppy.locale, this.opts.locale ])
+    this.translator = new Translator([ this.defaultLocale, this.uppy.locale, this.opts.locale ])
     this.i18n = this.translator.translate.bind(this.translator)
     this.i18nArray = this.translator.translateArray.bind(this.translator)
 
     this._prepareUpload = this._prepareUpload.bind(this)
     this._afterUpload = this._afterUpload.bind(this)
-    this._handleError = this._handleError.bind(this)
+    this._onError = this._onError.bind(this)
+    this._onCancelAll = this._onCancelAll.bind(this)
     this._onFileUploadURLAvailable = this._onFileUploadURLAvailable.bind(this)
     this._onRestored = this._onRestored.bind(this)
     this._getPersistentData = this._getPersistentData.bind(this)
@@ -113,10 +113,10 @@ module.exports = class Transloadit extends Plugin {
     // We only replace the hostname for Transloadit's companions, so that
     // people can also self-host them while still using Transloadit for encoding.
     let remote = file.remote
-    if (file.remote && TL_UPPY_SERVER.test(file.remote.serverUrl)) {
+    if (file.remote && TL_UPPY_SERVER.test(file.remote.companionUrl)) {
       const err = new Error(
         'The https://api2.transloadit.com/uppy-server endpoint was renamed to ' +
-        'https://api2.transloadit.com/companion, please update your `serverUrl` ' +
+        'https://api2.transloadit.com/companion, please update your `companionUrl` ' +
         'options accordingly.')
       // Explicitly log this error here because it is caught by the `createAssembly`
       // Promise further along.
@@ -126,16 +126,16 @@ module.exports = class Transloadit extends Plugin {
       throw err
     }
 
-    if (file.remote && TL_COMPANION.test(file.remote.serverUrl)) {
+    if (file.remote && TL_COMPANION.test(file.remote.companionUrl)) {
       const newHost = status.companion_url
         .replace(/\/$/, '')
       const path = file.remote.url
-        .replace(file.remote.serverUrl, '')
+        .replace(file.remote.companionUrl, '')
         .replace(/^\//, '')
 
       remote = {
         ...file.remote,
-        serverUrl: newHost,
+        companionUrl: newHost,
         url: `${newHost}/${path}`
       }
     }
@@ -329,6 +329,29 @@ module.exports = class Transloadit extends Plugin {
     })
   }
 
+  _cancelAssembly (assembly) {
+    return this.client.cancelAssembly(assembly).then(() => {
+      // TODO bubble this through AssemblyWatcher so its event handlers can clean up correctly
+      this.uppy.emit('transloadit:assembly-cancelled', assembly)
+    })
+  }
+
+  /**
+   * When all files are removed, cancel in-progress Assemblies.
+   */
+  _onCancelAll () {
+    const { assemblies } = this.getPluginState()
+
+    const cancelPromises = Object.keys(assemblies).map((assemblyID) => {
+      const assembly = this.getAssembly(assemblyID)
+      return this._cancelAssembly(assembly)
+    })
+
+    Promise.all(cancelPromises).catch((err) => {
+      this.uppy.log(err)
+    })
+  }
+
   /**
    * Custom state serialization for the Golden Retriever plugin.
    * It will pass this back to the `_onRestored` function.
@@ -632,8 +655,8 @@ module.exports = class Transloadit extends Plugin {
     })
   }
 
-  _handleError (err, uploadID) {
-    this.uppy.log(`[Transloadit] _handleError in upload ${uploadID}`)
+  _onError (err, uploadID) {
+    this.uppy.log(`[Transloadit] _onError in upload ${uploadID}`)
     this.uppy.log(err)
     const state = this.getPluginState()
     const assemblyIDs = state.uploadsAssemblies[uploadID]
@@ -650,7 +673,10 @@ module.exports = class Transloadit extends Plugin {
     this.uppy.addPostProcessor(this._afterUpload)
 
     // We may need to close socket.io connections on error.
-    this.uppy.on('error', this._handleError)
+    this.uppy.on('error', this._onError)
+
+    // Handle cancellation.
+    this.uppy.on('cancel-all', this._onCancelAll)
 
     if (this.opts.importFromUploadURLs) {
       // No uploader needed when importing; instead we take the upload URL from an existing uploader.
@@ -681,21 +707,38 @@ module.exports = class Transloadit extends Plugin {
       // Contains result data from Transloadit.
       results: []
     })
+
+    // We cannot cancel individual files because Assemblies tend to contain many files.
+    const { capabilities } = this.uppy.getState()
+    this.uppy.setState({
+      capabilities: {
+        ...capabilities,
+        individualCancellation: false
+      }
+    })
   }
 
   uninstall () {
     this.uppy.removePreProcessor(this._prepareUpload)
     this.uppy.removePostProcessor(this._afterUpload)
-    this.uppy.off('error', this._handleError)
+    this.uppy.off('error', this._onError)
 
     if (this.opts.importFromUploadURLs) {
       this.uppy.off('upload-success', this._onFileUploadURLAvailable)
     }
+
+    const { capabilities } = this.uppy.getState()
+    this.uppy.setState({
+      capabilities: {
+        ...capabilities,
+        individualCancellation: true
+      }
+    })
   }
 
   getAssembly (id) {
-    const state = this.getPluginState()
-    return state.assemblies[id]
+    const { assemblies } = this.getPluginState()
+    return assemblies[id]
   }
 
   getAssemblyFiles (assemblyID) {

+ 1 - 1
packages/@uppy/tus/package.json

@@ -24,7 +24,7 @@
   "dependencies": {
     "@uppy/companion-client": "0.28.4",
     "@uppy/utils": "0.30.4",
-    "tus-js-client": "github:ifedapoolarewaju/tus-js-client#888bcf73b66698a165f086f7bbe61951597f5c1b"
+    "tus-js-client": "github:ifedapoolarewaju/tus-js-client#d63fcf08ee43080a0e1cc968ad5986ace788c2d4"
   },
   "devDependencies": {
     "@uppy/core": "0.30.4"

+ 2 - 1
packages/@uppy/tus/src/index.js

@@ -206,6 +206,7 @@ module.exports = class Tus extends Plugin {
 
       this.onCancelAll(file.id, () => {
         this.resetUploaderReferences(file.id)
+        resolve(`upload ${file.id} was canceled`)
       })
 
       this.onResumeAll(file.id, () => {
@@ -271,7 +272,7 @@ module.exports = class Tus extends Plugin {
   connectToServerSocket (file) {
     return new Promise((resolve, reject) => {
       const token = file.serverToken
-      const host = getSocketHost(file.remote.serverUrl)
+      const host = getSocketHost(file.remote.companionUrl)
       const socket = new Socket({ target: `${host}/api/${token}` })
       this.uploaderSockets[file.id] = socket
       this.uploaderEvents[file.id] = createEventTracker(this.uppy)

+ 7 - 9
packages/@uppy/url/src/index.js

@@ -15,12 +15,12 @@ module.exports = class Url extends Plugin {
     this.id = this.opts.id || 'Url'
     this.title = this.opts.title || 'Link'
     this.type = 'acquirer'
-    this.icon = () => <svg aria-hidden="true" width="23" height="23" viewBox="0 0 23 23" xmlns="http://www.w3.org/2000/svg">
+    this.icon = () => <svg aria-hidden="true" width="23" height="23" viewBox="0 0 23 23">
       <path d="M20.485 11.236l-2.748 2.737c-.184.182-.367.365-.642.547-1.007.73-2.107 1.095-3.298 1.095-1.65 0-3.298-.73-4.398-2.19-.275-.365-.183-1.003.183-1.277.367-.273 1.008-.182 1.283.183 1.191 1.642 3.482 1.915 5.13.73a.714.714 0 0 0 .367-.365l2.75-2.737c1.373-1.46 1.373-3.74-.093-5.108a3.72 3.72 0 0 0-5.13 0L12.33 6.4a.888.888 0 0 1-1.283 0 .88.88 0 0 1 0-1.277l1.558-1.55a5.38 5.38 0 0 1 7.605 0c2.29 2.006 2.382 5.564.274 7.662zm-8.979 6.294L9.95 19.081a3.72 3.72 0 0 1-5.13 0c-1.467-1.368-1.467-3.74-.093-5.108l2.75-2.737.366-.365c.824-.547 1.74-.82 2.748-.73 1.008.183 1.833.639 2.382 1.46.275.365.917.456 1.283.182.367-.273.458-.912.183-1.277-.916-1.186-2.199-1.915-3.573-2.098-1.374-.273-2.84.091-4.031 1.004l-.55.547-2.749 2.737c-2.107 2.189-2.015 5.655.092 7.753C4.727 21.453 6.101 22 7.475 22c1.374 0 2.749-.547 3.848-1.55l1.558-1.551a.88.88 0 0 0 0-1.278c-.367-.364-1.008-.456-1.375-.09z" fill="#FF814F" fill-rule="nonzero" />
     </svg>
 
     // Set default options and locale
-    const defaultLocale = {
+    this.defaultLocale = {
       strings: {
         import: 'Import',
         enterUrlToImport: 'Enter URL to import a file',
@@ -29,18 +29,16 @@ module.exports = class Url extends Plugin {
       }
     }
 
-    const defaultOptions = {
-      locale: defaultLocale
-    }
+    const defaultOptions = {}
 
     this.opts = Object.assign({}, defaultOptions, opts)
 
     // i18n
-    this.translator = new Translator([ defaultLocale, this.uppy.locale, this.opts.locale ])
+    this.translator = new Translator([ this.defaultLocale, this.uppy.locale, this.opts.locale ])
     this.i18n = this.translator.translate.bind(this.translator)
     this.i18nArray = this.translator.translateArray.bind(this.translator)
 
-    this.hostname = this.opts.serverUrl
+    this.hostname = this.opts.companionUrl
 
     if (!this.hostname) {
       throw new Error('Companion hostname is required, please consult https://uppy.io/docs/companion')
@@ -56,7 +54,7 @@ module.exports = class Url extends Plugin {
     this.handlePaste = this.handlePaste.bind(this)
 
     this.client = new RequestClient(uppy, {
-      serverUrl: this.opts.serverUrl,
+      companionUrl: this.opts.companionUrl,
       serverHeaders: this.opts.serverHeaders
     })
   }
@@ -120,7 +118,7 @@ module.exports = class Url extends Plugin {
             url: url
           },
           remote: {
-            serverUrl: this.opts.serverUrl,
+            companionUrl: this.opts.companionUrl,
             url: `${this.hostname}/url/get`,
             body: {
               fileId: url,

+ 2 - 2
packages/@uppy/url/src/style.scss

@@ -18,7 +18,7 @@
 }
 
   .uppy-size--md .uppy-Url-input  {
-    margin-bottom: 30px;
+    margin-bottom: 20px;
   }
 
 .uppy-Url-importButton {
@@ -26,5 +26,5 @@
 }
 
   .uppy-size--md .uppy-Url-importButton {
-    padding: 13px 35px;
+    padding: 13px 30px;
   }

+ 1 - 1
packages/@uppy/url/types/index.d.ts

@@ -2,7 +2,7 @@ import Uppy = require('@uppy/core');
 
 declare module Url {
   export interface UrlOptions extends Uppy.PluginOptions {
-    serverUrl: string;
+    companionUrl: string;
     // TODO inherit from ProviderOptions
   }
 }

+ 1 - 1
packages/@uppy/webcam/src/CameraIcon.js

@@ -1,7 +1,7 @@
 const { h } = require('preact')
 
 module.exports = (props) => {
-  return <svg aria-hidden="true" fill="#0097DC" width="66" height="55" viewBox="0 0 66 55" xmlns="http://www.w3.org/2000/svg">
+  return <svg aria-hidden="true" fill="#0097DC" width="66" height="55" viewBox="0 0 66 55">
     <path d="M57.3 8.433c4.59 0 8.1 3.51 8.1 8.1v29.7c0 4.59-3.51 8.1-8.1 8.1H8.7c-4.59 0-8.1-3.51-8.1-8.1v-29.7c0-4.59 3.51-8.1 8.1-8.1h9.45l4.59-7.02c.54-.54 1.35-1.08 2.16-1.08h16.2c.81 0 1.62.54 2.16 1.08l4.59 7.02h9.45zM33 14.64c-8.62 0-15.393 6.773-15.393 15.393 0 8.62 6.773 15.393 15.393 15.393 8.62 0 15.393-6.773 15.393-15.393 0-8.62-6.773-15.393-15.393-15.393zM33 40c-5.648 0-9.966-4.319-9.966-9.967 0-5.647 4.318-9.966 9.966-9.966s9.966 4.319 9.966 9.966C42.966 35.681 38.648 40 33 40z" fill-rule="evenodd" />
   </svg>
 }

+ 2 - 3
packages/@uppy/webcam/src/index.js

@@ -43,7 +43,7 @@ module.exports = class Webcam extends Plugin {
     this.type = 'acquirer'
     this.icon = CameraIcon
 
-    const defaultLocale = {
+    this.defaultLocale = {
       strings: {
         smile: 'Smile!',
         takePicture: 'Take a picture',
@@ -58,7 +58,6 @@ module.exports = class Webcam extends Plugin {
     const defaultOptions = {
       onBeforeSnapshot: () => Promise.resolve(),
       countdown: false,
-      locale: defaultLocale,
       modes: [
         'video-audio',
         'video-only',
@@ -73,7 +72,7 @@ module.exports = class Webcam extends Plugin {
     this.opts = Object.assign({}, defaultOptions, opts)
 
     // i18n
-    this.translator = new Translator([ defaultLocale, this.uppy.locale, this.opts.locale ])
+    this.translator = new Translator([ this.defaultLocale, this.uppy.locale, this.opts.locale ])
     this.i18n = this.translator.translate.bind(this.translator)
     this.i18nArray = this.translator.translateArray.bind(this.translator)
 

+ 10 - 10
packages/@uppy/webcam/src/style.scss

@@ -15,7 +15,7 @@
   flex: 1;
   flex-grow: 1;
   overflow: hidden;
-  background-color: $color-black;
+  background-color: $gray-800;
   text-align: center;
   position: relative;
 }
@@ -44,7 +44,7 @@
 .uppy-Webcam-buttonContainer {
   width: 100%;
   height: 75px;
-  border-top: 1px solid rgba($color-gray, 0.2);
+  border-top: 1px solid $gray-200;
   display: flex;
   align-items: center;
   justify-content: center;
@@ -55,8 +55,8 @@
   width: 45px;
   height: 45px;
   border-radius: 50%;
-  background-color: $color-red;
-  color: $color-white;
+  background-color: $red;
+  color: $white;
   cursor: pointer;
   transition: all 0.3s;
 }
@@ -78,12 +78,12 @@
   }
 
   .uppy-Webcam-button:hover {
-    background-color: darken($color-red, 10%);
+    background-color: darken($red, 5%);
   }
 
   .uppy-Webcam-button:focus {
     outline: none;
-    box-shadow: 0 0 0 0.2rem rgba($color-cornflower-blue, 0.5);
+    box-shadow: 0 0 0 0.2rem rgba($blue, 0.5);
   }
 
 .uppy-Webcam-button--picture {
@@ -110,23 +110,23 @@
   line-height: 1.35;
   font-weight: 400;
   margin: 0;
-  margin-bottom: 15px;
+  margin-bottom: 5px;
   padding: 0 15px;
   max-width: 500px;
   text-align: center;
-  color: $color-black;
+  color: $gray-800;
 }
 
 .uppy-Webcam-permissons p {
   text-align: center;
   line-height: 1.45;
-  color: $color-gray;
+  color: $gray-500;
   margin: 0;
 }
 
 .uppy-Webcam-permissonsIcon svg {
   width: 100px;
   height: 75px;
-  color: lighten($color-gray, 15%);
+  color: $gray-400;
   margin-bottom: 30px;
 }

+ 13 - 10
packages/@uppy/xhr-upload/src/index.js

@@ -28,7 +28,7 @@ module.exports = class XHRUpload extends Plugin {
     this.id = 'XHRUpload'
     this.title = 'XHRUpload'
 
-    const defaultLocale = {
+    this.defaultLocale = {
       strings: {
         timedOut: 'Upload stalled for %{seconds} seconds, aborting.'
       }
@@ -43,7 +43,6 @@ module.exports = class XHRUpload extends Plugin {
       responseUrlFieldName: 'url',
       bundle: false,
       headers: {},
-      locale: defaultLocale,
       timeout: 30 * 1000,
       limit: 0,
       withCredentials: false,
@@ -90,7 +89,7 @@ module.exports = class XHRUpload extends Plugin {
     this.opts = Object.assign({}, defaultOptions, opts)
 
     // i18n
-    this.translator = new Translator([ defaultLocale, this.uppy.locale, this.opts.locale ])
+    this.translator = new Translator([ this.defaultLocale, this.uppy.locale, this.opts.locale ])
     this.i18n = this.translator.translate.bind(this.translator)
     this.i18nArray = this.translator.translateArray.bind(this.translator)
 
@@ -333,7 +332,7 @@ module.exports = class XHRUpload extends Plugin {
       )
       .then((res) => {
         const token = res.token
-        const host = getSocketHost(file.remote.serverUrl)
+        const host = getSocketHost(file.remote.companionUrl)
         const socket = new Socket({ target: `${host}/api/${token}` })
 
         socket.on('progress', (progressData) => emitSocketProgress(this, progressData, file))
@@ -507,10 +506,12 @@ module.exports = class XHRUpload extends Plugin {
 
   install () {
     if (this.opts.bundle) {
+      const { capabilities } = this.uppy.getState()
       this.uppy.setState({
-        capabilities: Object.assign({}, this.uppy.getState().capabilities, {
-          bundled: true
-        })
+        capabilities: {
+          ...capabilities,
+          individualCancellation: false
+        }
       })
     }
 
@@ -519,10 +520,12 @@ module.exports = class XHRUpload extends Plugin {
 
   uninstall () {
     if (this.opts.bundle) {
+      const { capabilities } = this.uppy.getState()
       this.uppy.setState({
-        capabilities: Object.assign({}, this.uppy.getState().capabilities, {
-          bundled: true
-        })
+        capabilities: {
+          ...capabilities,
+          individualCancellation: true
+        }
       })
     }
 

+ 2 - 0
packages/uppy/index.js

@@ -38,3 +38,5 @@ exports.Form = require('@uppy/form')
 exports.GoldenRetriever = require('@uppy/golden-retriever')
 exports.ReduxDevTools = require('@uppy/redux-dev-tools')
 exports.ThumbnailGenerator = require('@uppy/thumbnail-generator')
+
+exports.locales = {}

+ 2 - 2
packages/uppy/types/uppy-tests.ts

@@ -17,8 +17,8 @@ import * as Uppy from '../';
 (() => {
   const uppy = Uppy.Core({ autoProceed: false })
     .use(Uppy.Dashboard, { trigger: '#select-files' })
-    .use(Uppy.GoogleDrive, { target: Uppy.Dashboard, serverUrl: 'https://companion.uppy.io' })
-    .use(Uppy.Instagram, { target: Uppy.Dashboard, serverUrl: 'https://companion.uppy.io' })
+    .use(Uppy.GoogleDrive, { target: Uppy.Dashboard, companionUrl: 'https://companion.uppy.io' })
+    .use(Uppy.Instagram, { target: Uppy.Dashboard, companionUrl: 'https://companion.uppy.io' })
     .use(Uppy.Webcam, { target: Uppy.Dashboard })
     .use(Uppy.Tus, { endpoint: 'https://master.tus.io/files/' })
     .on('complete', (result) => {

+ 1 - 1
test/endtoend/create-react-app/src/App.js

@@ -15,7 +15,7 @@ class App extends Component {
 
     this.uppy = new Uppy({ id: 'uppy1', autoProceed: true, debug: true })
       .use(Tus, { endpoint: 'https://master.tus.io/files/' })
-      .use(GoogleDrive, { serverUrl: 'https://companion.uppy.io' })
+      .use(GoogleDrive, { companionUrl: 'https://companion.uppy.io' })
 
     this.uppy2 = new Uppy({ id: 'uppy2', autoProceed: false, debug: true })
       .use(Tus, { endpoint: 'https://master.tus.io/files/' })

+ 3 - 3
test/endtoend/providers/main.js

@@ -16,7 +16,7 @@ Uppy({
     target: '#uppyDashboard',
     inline: true
   })
-  .use(GoogleDrive, { target: Dashboard, serverUrl: 'http://localhost:3020' })
-  .use(Instagram, { target: Dashboard, serverUrl: 'http://localhost:3020' })
-  .use(Dropbox, { target: Dashboard, serverUrl: 'http://localhost:3020' })
+  .use(GoogleDrive, { target: Dashboard, companionUrl: 'http://localhost:3020' })
+  .use(Instagram, { target: Dashboard, companionUrl: 'http://localhost:3020' })
+  .use(Dropbox, { target: Dashboard, companionUrl: 'http://localhost:3020' })
   .use(Tus, { endpoint: 'https://master.tus.io/files/' })

+ 4 - 4
test/endtoend/typescript/main.ts

@@ -32,10 +32,10 @@ const uppy = Core({
     proudlyDisplayPoweredByUppy: true,
     note: '2 files, images and video only'
   })
-  .use(GoogleDrive, { target: Dashboard, serverUrl: 'http://localhost:3020' })
-  .use(Instagram, { target: Dashboard, serverUrl: 'http://localhost:3020' })
-  .use(Dropbox, { target: Dashboard, serverUrl: 'http://localhost:3020' })
-  .use(Url, { target: Dashboard, serverUrl: 'http://localhost:3020' })
+  .use(GoogleDrive, { target: Dashboard, companionUrl: 'http://localhost:3020' })
+  .use(Instagram, { target: Dashboard, companionUrl: 'http://localhost:3020' })
+  .use(Dropbox, { target: Dashboard, companionUrl: 'http://localhost:3020' })
+  .use(Url, { target: Dashboard, companionUrl: 'http://localhost:3020' })
   .use(Webcam, { target: Dashboard })
   .use(Tus, { endpoint: TUS_ENDPOINT })
   .use(Form, { target: '#upload-form' })

+ 1 - 1
test/endtoend/url-plugin/main.js

@@ -16,7 +16,7 @@ function initUrlPlugin (companionUrl) {
     })
     .use(Url, {
       target: Dashboard,
-      serverUrl: companionUrl
+      companionUrl: companionUrl
     })
     .use(Tus, { endpoint: 'https://master.tus.io/files/' })
 }

+ 4 - 1
website/inject.js

@@ -12,6 +12,7 @@ const touch = require('touch')
 const webRoot = __dirname
 const uppyRoot = path.join(__dirname, '../packages/uppy')
 const robodogRoot = path.join(__dirname, '../packages/@uppy/robodog')
+const localesRoot = path.join(__dirname, '../packages/@uppy/locales')
 
 const configPath = path.join(webRoot, '/themes/uppy/_config.yml')
 const { version } = require(path.join(uppyRoot, '/package.json'))
@@ -113,8 +114,10 @@ async function injectSizes (config) {
 async function injectBundles () {
   const cmds = [
     `mkdir -p ${path.join(webRoot, '/themes/uppy/source/uppy')}`,
+    `mkdir -p ${path.join(webRoot, '/themes/uppy/source/uppy/locales')}`,
     `cp -vfR ${path.join(uppyRoot, '/dist/*')} ${path.join(webRoot, '/themes/uppy/source/uppy/')}`,
-    `cp -vfR ${path.join(robodogRoot, '/dist/*')} ${path.join(webRoot, '/themes/uppy/source/uppy/')}`
+    `cp -vfR ${path.join(robodogRoot, '/dist/*')} ${path.join(webRoot, '/themes/uppy/source/uppy/')}`,
+    `cp -vfR ${path.join(localesRoot, '/dist/*')} ${path.join(webRoot, '/themes/uppy/source/uppy/locales')}`
   ].join(' && ')
 
   const { stdout } = await promisify(exec)(cmds)

+ 2 - 2
website/package.json

@@ -3,7 +3,7 @@
   "version": "0.0.1",
   "private": true,
   "hexo": {
-    "version": "3.7.1"
+    "version": "3.8.0"
   },
   "dependencies": {
     "autoprefixer": "^7.2.6",
@@ -44,4 +44,4 @@
     "node-notifier": "^5.2.1",
     "watchify": "^3.11.0"
   }
-}
+}

+ 1 - 1
website/src/_posts/2019-04-liftoff-14.md

@@ -39,4 +39,4 @@ While there are still things to be implemented, such as picking files from Insta
 
 - The whole team will again do a call this afternoon to reassess our roadmap and see where all we stand. Some new tasks were added on [Friday](/blog/2019/04/liftoff-11/), so we'll also have to see about getting those into gear.
 
-That's it for Day 14. Tomorrow, it will be Samuel's turn again to update you on our board and progress. Subscribe via [Twitter](https://twitter.com/uppy_io) or [RSS](https://uppy.io/atom.xml) and don't miss out on Day 15! :dog:
+That's it for Day 14. Tomorrow, it will be Samuel's turn again to update you on our board and progress. Subscribe via [Twitter](https://twitter.com/uppy_io) or [RSS](https://uppy.io/atom.xml) and don't miss out on [Day 15](/blog/2019/04/liftoff-15/)! :dog:

部分文件因文件數量過多而無法顯示